数组数据在嵌套的异步箭头函数循环中丢失

时间:2019-02-07 08:59:09

标签: javascript ajax ecmascript-6 es6-promise arrow-functions

thread详细说明了同步与异步和可能的解决方案之间的区别,但是我已经在使用一种解决方案,并且仍然会出错。 我想我在这里达到了ES的理解极限,所以我真的需要在此问题上获得帮助,因为我只是不明白为什么它会丢失。以下是我在nuxt项目中使用的代码段,但它与它无关,因为我从后端express移植了该代码段。

async fetch({store, error}) {
  let series = '', courses = [], album = {}
  store.state.courses.forEach(async course => {
    album = {...course}
    series = course.uri.split('/')[2]
    try {
     const {data: {data}} = await axios.get('http://localhost:3000/luvlyapi/videos', {
      params: {
        series  //? album id
      }
    })
    album['videos'] = data
    courses.push(album)
    console.log('loop', courses)
  } catch (err) {
    error({statusCode: err.statusCode, message: err})
  }
})
console.log({courses})
store.commit('SET_COURSES', courses)

} result

您可以看到数组已被推送,但循环结束后仍然为空。

2 个答案:

答案 0 :(得分:3)

.forEach()不等待您的异步回调。它只是愉快地继续循环,您记录了courses的值,然后过了一段时间,回调中的所有await操作都完成了,您将值放入了courses中。因此,在将数据放入变量之前,您需要先查看变量的时间。

值得理解的是,await仅暂停本地函数执行(最接近的函数作用域),在您的情况下,该函数就是.forEach()回调。它不会导致任何更高级别的暂停。而且,由于.forEach()甚至没有查看回调返回的内容,因此肯定不是在查看async回调返回的承诺。因此,一旦您点击await,您的async回调就会返回一个承诺。 .forEach()循环会看到回调返回,即使您在回调内部的await尚未完成,也会立即启动循环的下一个迭代。

重要的是要了解await仅暂停本地函数,而不暂停其上方的任何内容,也不暂停任何调用方。当您的async函数达到第一个await时,该函数将向外界返回一个承诺。为了使外部世界也能停下来,它必须履行.forEach()不会做出的承诺。

另一方面,for循环将await保留在实际函数中,因此整个范围被await暂停。 如果您希望循环实际上以await暂停,请使用for循环,而不是.forEach()循环。

例如,您可以执行以下操作:

async fetch({store, error}) {
    let series = '',  courses = [],  album = {};
    for (let course of store.state.courses) {
        album = {...course}
        series = course.uri.split('/')[2]
        try {
            const {data: {data}} = await axios.get('http://localhost:3000/luvlyapi/videos', {
                params: {
                    series //? album id
                }
            })
            album['videos'] = data
            courses.push(album)
            console.log('loop', courses)
        } catch (err) {
            error({
                statusCode: err.statusCode,
                message: err
            })
        }
    }
    console.log({courses})
    store.commit('SET_COURSES', courses)
}

仅供参考,您的原始实现还存在一个问题,即在多个异步回调之间共享更高级别的范围内的变量,这些回调同时处于运行状态,这将导致一个踩到另一个。解决方案是要么像我的代码示例所示并序列化异步调用,以使其不在同一时间运行,要么将变量声明为异步作用域的局部变量,以便每个异步回调都具有自己的单独变量。

答案 1 :(得分:0)

尝试按以下方式使用Promise.all(从head编写代码)

async fetch({ store, error }) {
  let series = '', courses = [], album = {}
  let pr = store.state.courses.map(course => {
    return new Promise(resolve => {
      album = { ...course }
      series = course.uri.split('/')[2]
      try {
        const { data: { data } } = await axios.get('http://localhost:3000/luvlyapi/videos', {
          params: {
            series  //? album id
          }
        })
        album['videos'] = data
        courses.push(album)
        console.log('loop', courses)
        resolve();
      } catch (err) {
        error({ statusCode: err.statusCode, message: err })
      }
    })
  });

  await Promise.all(pr);

  console.log({ courses })
  store.commit('SET_COURSES', courses)
}

问题在于在forEach内部使用await-不会等待...更多详细信息here