map中使用await为什么会失效?

问题发生的场景:页面渲染时,会加载一个n行的列表,列表每一行对应一组数据,一组数据由一个请求获得。我们需要将请求获取到的数据按照一定顺序显示到页面上

下面先看一下数据大致的请求与存储方式:

dataSource = [obj1, obj2, objn];
 
getData = (dataSource) => {
  let selectArr = [];
  let { title } = this.props;
 
  _.map(dataSource, async (item, index) => {
 
    // 请求数据
    await getBasicPlans(title, item.id).then(data => {
      // 将数据按照请求时的顺序保存进数组(optionsArr是一个数组,里面保存着option中的一条条数据)
      selectArr.splice(index, 0, data.optionsArr);
      this.setState({ selectArr });
    })

  })
}

可以看到,我们将数据存储进数组时,使用了遍历时的index,并且还有await进行同步化的处理。按理来说,请求完了第一条数据才会请求请求第二条数据,这样一来,splice中的index就是从0→1→2依次增大,那么selectArr中存储的数据就是有序的

但结果确是selectArr中存储的数据是无序的

我们在.then中打印index来看一下:

img

可以看到,并没有按照我们预想的那样按照顺序将index打印出来

  1. 也就是说map种使用的await并没有生效,为什么呢?
  2. 看到很多博主说for of使用了迭代器,可以解决这一问题,但是一个朋友仅仅将上面的map换成了最基本的for循环就实现了,为什么最基本的for循环就能解决这一问题呢?

map/forEach内部使用了while结合callback方式来执行函数,await不会等待callback的执行
可以这样写

await Promise.all(
  a.map(dataSource, (item, index) => {
    // 请求数据
    await getBasicPlans(title, item.id).then(data => {
      // 将数据按照请求时的顺序保存进数组(optionsArr是一个数组,里面保存着option中的一条条数据)
      selectArr.splice(index, 0, data.optionsArr);
      this.setState({ selectArr });
  })
    })
);

这样写一点好处都没有,会导致速度慢,影响客户体验

这个async位置应该在map里面吧,可以试试这样可不可以

img

 _.map(dataSource, async (item, index) => {
    // 请求数据
    await getBasicPlans(title, item.id).then(data => {
      // 将数据按照请求时的顺序保存进数组(optionsArr是一个数组,里面保存着option中的一条条数据)
      selectArr.splice(index, 0, data.optionsArr);
      this.setState({ selectArr });
    })
 
  })

对你有用的话,请点击一下【采纳此答案】,谢谢🌹

我的理解,既然你是使用async/await关键字 await不可以单独使用 一定要跟着async
一般是这样用 async A(){ await ....} 或者 const A = async ()=>{await... } 这想必你都知道
_.map(dataSource, (item, index) => {})那这里的 (item, index) => {}是不是一个函数 那么你在这个函数里面用了await 关键字 ,那是不是应该也必须得有async

不知这样加可不可以

    getData = async (dataSource) => {
      let selectArr = [];
      let { title } = this.props;

     await _.map(dataSource, **async** (item, index) => {

        // 请求数据
        await getBasicPlans(title, item.id).then(data => {
          // 将数据按照请求时的顺序保存进数组(optionsArr是一个数组,里面保存着option中的一条条数据)
          selectArr.splice(index, 0, data.optionsArr);
          this.setState({ selectArr });
        })

      })
    }
  }

map 会创建出多个回调函数,多个回调函数被加上了各自的 async、await:

// map遍历得到的结果
async ()=>{
  await getBasicPlans();  // getBasicPlans的返回值是一个 Promise 对象
} 
async ()=>{ 
  await getBasicPlans();
} 
async ()=>{ 
  await getBasicPlans();
} 

各个函数之间是独立的,彼此的回调也是独立的
请求是异步的,彼此之间又没有关联,顺序也就自然无法保证
如果返回的数据是这样的(类似于 for 循环的使用结果):

// for 循环产生的一个个异步请求之间的关系
await getBasicPlans();
await getBasicPlans();
await getBasicPlans();

可以在一定程度解决 “顺序” 的目的
但是这样的方式,就需要等待上一个请求响应了,才能够发送下一个请求。花费的时间总量等于各个请求所消耗的时间之和。如果请求较多,这种方式就会非常占用时间
可以看的出来,这种方式在实现方式上,就是说依旧是同步的。

最完美的解决方案是使用 promise.all

async ()=>{
  promise.all(arr.map(() => {
    return getBasicPlans();
  })).then(data => {
    data...
  })
} 

通过 map 遍历得到一个个的 Promise 实例化对象

async ()=>{
  promise.all([promise1, promise2, promise3 ... ]).then(data => {
    data...
  })
} 

由 promise.all 的实现机制,数组内后一个 promise 的返回不需要依赖前一个 promise 的状态,只需要保证生成 promise 对象的函数是有序的即可
恰巧,map 就只能保证依次去创建并执行回调函数,而不会去在意结果返回
我们知道,map 创建的回调函数是并行执行的,那么,promise 数组创建完成的时间,就由消耗时间最久的请求决定
接下来再依次去拿着数组中的每一个 promise 去执行 .then

可以看到,在这种场景下,promise.all 结合着 map 来使用,异步并行的同时,还可以完美的保证顺序,的确是一个非常完美的解决方案。