在forEach回调函数中存在异步操作的处理

我们会碰到这种情形:


const result = [];
const ids = [1, 2, 3, 4, 5];
ids.forEach(id => {
	const list = http(id); //http为一个异步请求,拿到对应id的信息
	result.push(list.name); 
})
console.log(result); // []

结果事与愿违是一个空的数组,其实我们希望的结果打印对应id的信息name;原因在于http是一个异步请求,那么forEach的函数回调则会把异步请求放入事件队列里,然后执行console.log(result),最后将事件队列里的函数放入主线程执行,所以我们看到打印的结果是一个空数组,如果我们在console.log(result)后面加上这么一段代码setTimeout(() => {console.log(result)}),那么其打印的结果就是我们所要得到的信息名称;所以我们想着能不能不通过这种setTimeout去拿到我们想要的结果,而是通过在异步请求前加入await来实现我们要的结果:


const result = [];
const ids = [1, 2, 3, 4, 5];
ids.forEach(async (id) => {
	const list = await http(id); //http为一个异步请求,拿到对应id的信息
	result.push(list.name); 
})
console.log(result); // []

结果出乎意料,依然打印的是一个空的数组,所以我们这时候就需要来看看forEach方法是怎么封装的了,这是forEach方法的一段源码:

// Production steps of ECMA-262, Edition 5, 15.4.4.18
// Reference: http://es5.github.io/#x15.4.4.18
if (!Array.prototype.forEach) {

  Array.prototype.forEach = function(callback, thisArg) {

    var T, k;

    if (this == null) {
      throw new TypeError(' this is null or not defined');
    }

    // 1. Let O be the result of calling toObject() passing the
    // |this| value as the argument.
    var O = Object(this);

    // 2. Let lenValue be the result of calling the Get() internal
    // method of O with the argument "length".
    // 3. Let len be toUint32(lenValue).
    var len = O.length >>> 0;

    // 4. If isCallable(callback) is false, throw a TypeError exception. 
    // See: http://es5.github.com/#x9.11
    if (typeof callback !== "function") {
      throw new TypeError(callback + ' is not a function');
    }

    // 5. If thisArg was supplied, let T be thisArg; else let
    // T be undefined.
    if (arguments.length > 1) {
      T = thisArg;
    }

    // 6. Let k be 0
    k = 0;

    // 7. Repeat, while k < len
    while (k < len) {

      var kValue;

      // a. Let Pk be ToString(k).
      //    This is implicit for LHS operands of the in operator
      // b. Let kPresent be the result of calling the HasProperty
      //    internal method of O with argument Pk.
      //    This step can be combined with c
      // c. If kPresent is true, then
      if (k in O) {

        // i. Let kValue be the result of calling the Get internal
        // method of O with argument Pk.
        kValue = O[k];

        // ii. Call the Call internal method of callback with T as
        // the this value and argument list containing kValue, k, and O.
        callback.call(T, kValue, k, O);
      }
      // d. Increase k by 1.
      k++;
    }
    // 8. return undefined
  };
}

我们可以看到callback.call(T, kValue, k, o)的执行是一个同步执行,所以我们的回调函数加了async依然不能得到我们想要的结果,因为http前加的await只能保证回调函数在执行中的http先执行,而后执行result.push(list.name);并不能保证forEach方法的回调函数全部执行后,再打印console.log(result);所以我们来改造一下forEach方法:


const result = [];
const ids = [1, 2, 3, 4, 5];
ids.forEach(async (id) => {
	const list = await http(id); //http为一个异步请求,拿到对应id的信息
	result.push(list.name); 
})
console.log(result); // []

结果出乎意料,依然打印的是一个空的数组,所以我们这时候就需要来看看forEach方法是怎么封装的了,这是forEach方法的一段源码:

// Production steps of ECMA-262, Edition 5, 15.4.4.18
// Reference: http://es5.github.io/#x15.4.4.18
if (!Array.prototype.forEach) {

  Array.prototype.forEach = async function(callback, thisArg) {

    var T, k;

    if (this == null) {
      throw new TypeError(' this is null or not defined');
    }

    // 1. Let O be the result of calling toObject() passing the
    // |this| value as the argument.
    var O = Object(this);

    // 2. Let lenValue be the result of calling the Get() internal
    // method of O with the argument "length".
    // 3. Let len be toUint32(lenValue).
    var len = O.length >>> 0;

    // 4. If isCallable(callback) is false, throw a TypeError exception. 
    // See: http://es5.github.com/#x9.11
    if (typeof callback !== "function") {
      throw new TypeError(callback + ' is not a function');
    }

    // 5. If thisArg was supplied, let T be thisArg; else let
    // T be undefined.
    if (arguments.length > 1) {
      T = thisArg;
    }

    // 6. Let k be 0
    k = 0;

    // 7. Repeat, while k < len
    while (k < len) {

      var kValue;

      // a. Let Pk be ToString(k).
      //    This is implicit for LHS operands of the in operator
      // b. Let kPresent be the result of calling the HasProperty
      //    internal method of O with argument Pk.
      //    This step can be combined with c
      // c. If kPresent is true, then
      if (k in O) {

        // i. Let kValue be the result of calling the Get internal
        // method of O with argument Pk.
        kValue = O[k];

        // ii. Call the Call internal method of callback with T as
        // the this value and argument list containing kValue, k, and O.
        await callback.call(T, kValue, k, O);
      }
      // d. Increase k by 1.
      k++;
    }
    // 8. return undefined
  };
}

我们将forEach定义成一个异步函数,在回调函数前加上await,这样我们在http前加的awati就可以实现我们想要的结果了;
现在问题又来了,我们能不能在不改forEach源码得前提下,使用其他方法来得到我们需要的结果呢,答案是肯定的:

  1. 利用Promise.all()

    
    const result = [];
    const ids = [1, 2, 3, 4, 5];
    const https = ids.map(id => {
    	return http(id); 
    });
    const res = await Promise.all(https);
    res.forEach(list => {
    	result.push(list.name);
    })
    console.log(result); // []
    
    
  2. 利用length属性

    
    const result = [];
    const ids = [1, 2, 3, 4, 5];
    const { length } = ids;
    async function request(){
    	return new Promise((resolve) => {
    		ids.forEach((id, i)=> {
    			const list = await http(id);
    			result.push(list.name);
    			if(i === length - 1) {
    				resolve(result);
    			}
    		})
    	});
    }
    await request();
    console.log(result);
    
上一篇:函数式编程-------概念基础部分


下一篇:原生for循环、forEach、for in 、 for of的区别