ForEach, Map, Reduce, and Promise

Through this article, you can learn:

  1. Can forEach, Map, and reduce be followed by async functions?
  2. How do you implement multiple promises that execute synchronously, gathering the results regardless of whether an error is thrown?

Can forEach be followed by async?

First let’s simulate an asynchronous function:

const fetch = (forceTrue) = > new Promise((resolve, reject) = > {
    if (Math.random() > 0.2 || forceTrue) {
        resolve('success');
    } else {
        reject('error');
 } }); Copy the code

Then let’s try using forEach to execute multiple async functions:

const result = [];

[0.1.2].forEach(async() = > {    const value = await fetch(true);
    result.push(value);
});  console.log(result); / / return [] Copy the code

As you can see, we expect to get the result of the fetch with await and store it in result, but we print “empty array” afterwards.

Let’s try using for:

const result = [];

for (let i = 0; i < 3; ++i) {
    const value = await fetch(true);
    result.push(value);
}  console.log(result); // Return ['success', 'success', 'success'] Copy the code

As you can see, “returns as expected” when using for. So why not forEach? ForEach polyfill MDN forEach Polyfill MDN

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 (typeofcallback ! = ="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  }; } Copy the code

Callback. call(T, kValue, k, O); Because it has no await in front of it, so every time the loop gets to this point it is not waiting for it to finish, and it will print the result as an empty array.

Can map and Reduce be followed by async functions?

Let’s look at the Map and Reduce demo again:

// map
const mapResult = [0.1.2].map(async() = >await fetch(true));
console.log(mapResult); Return [Promise, Promise, Promise]

// reduce
const reduceResult = [0.1.2].reduce(async (accu) => {  const value = await fetch(true);  accu.push(value);  console.log('accu====='.typeof accu);  return accu; } []);Uncaught (in promise) TypeError: Accu. Push is not a function console.log(reduceResult); Copy the code

MDN: polyfill MDN: polyfill MDN: Polyfill MDN: Polyfill MDN

Production Steps of ECMA-262, Edition 5, 15.4.4.19
/ / Reference: http://es5.github.io/#x15.4.4.19
if (!Array.prototype.map) {

  Array.prototype.map = function(callback/*, thisArg*/) {
  var T, A, 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 (typeofcallback ! = ='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 = arguments[1];  }   // 6. Let A be a new array created as if by the expression new Array(len)  // where Array is the standard built-in constructor with that name and  // len is the value of len.  A = new Array(len);   // 7. Let k be 0  k = 0;   // 8. Repeat, while k < len  while (k < len) {   var kValue, mappedValue;   // 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. Let mappedValue be the result of calling the Call internal  // method of callback with T as the this value and argument  // list containing kValue, k, and O.  mappedValue = callback.call(T, kValue, k, O);   // iii. Call the DefineOwnProperty internal method of A with arguments  // Pk, Property Descriptor  // { Value: mappedValue,  // Writable: true,  // Enumerable: true,  // Configurable: true },  // and false.   // In browsers that support Object.defineProperty, use the following:  // Object.defineProperty(A, k, {  // value: mappedValue,  // writable: true,  // enumerable: true,  // configurable: true  // });   // For best browser support, use the following:  A[k] = mappedValue;  }  // d. Increase k by 1.  k++;  }   // 9. return A  return A;  }; } Copy the code

As you can see in the source code, “every time the while loop assigns the result returned by the callback to mappedValue”, and the callback is called without await, so each value in the result array is a promise.

Polyfill MDN reduce polyfill MDN reduce reduce

// Production steps of ECMA-262, Edition 5, 15.4.4.21
/ / Reference: http://es5.github.io/#x15.4.4.21
// https://tc39.github.io/ecma262/#sec-array.prototype.reduce
if (!Array.prototype.reduce) {
  Object.defineProperty(Array.prototype, 'reduce', {
 value: function(callback /*, initialValue*/) {  if (this= = =null) {  throw new TypeError( 'Array.prototype.reduce ' +  'called on null or undefined' );  }  if (typeofcallback ! = ='function') {  throw new TypeError( callback +  ' is not a function');  }   // 1. Let O be ? ToObject(this value).  var o = Object(this);   // 2. Let len be ? ToLength(? Get(O, "length")).  var len = o.length >>> 0;   // Steps 3, 4, 5, 6, 7  var k = 0;  var value;   if (arguments.length >= 2) {  value = arguments[1];  } else {  while(k < len && ! (kin o)) {  k++;  }   // 3. If len is 0 and initialValue is not present,  // throw a TypeError exception.  if (k >= len) {  throw new TypeError( 'Reduce of empty array ' +  'with no initial value' );  }  value = o[k++];  }   // 8. Repeat, while k < len  while (k < len) {  // a. Let Pk be ! ToString(k).  // b. Let kPresent be ? HasProperty(O, Pk).  // c. If kPresent is true, then  // i. Let kValue be ? Get(O, Pk).  // ii. Let accumulator be ? Call(  // callbackfn, undefined,  « accumulator, kValue, k, O »).  if (k in o) {  value = callback(value, o[k], k, o);  }   // d. Increase k by 1.  k++;  }   // 9. Return accumulator.  return value;  }  }); } Copy the code

Accu is an async function, and accu is an async function. Accu is a promise and does not have an array method.

The solution

To be honest, methods like forEach, Map, Reduce, and filter are meant to be “for synchronous functions” and are not suitable for asynchronous scenarios. In asynchronous scenarios, “For and for of methods are recommended.”

But while they are all for synchronous functions, there are “hacks” that can make them work for asynchronous functions, such as reduce:

(async function () {
    const fetch = (forceTrue) = > new Promise((resolve, reject) = > {
        if (Math.random() > 0.2 || forceTrue) {
            resolve('success');
        } else {
 reject('error');  }  });   const reduceResult = await [0.1.2].reduce(async (accu) => {  const value = await fetch(true);  const resolvedAccu = await accu;  resolvedAccu.push(value);  return resolvedAccu; } []);  console.log('= = = =', reduceResult); }) ()Copy the code

The above code has these caveats:

  1. Since the accumulator Accu is the promise returned by the async callback function, we “await it”.
  2. Since the final result is also a promise, we added await before it, but “await can only be used in async functions”, so we added async to anonymous functions.

promise

The above reminds me of one use scenario for promises: We know promise.all can get synchronous promise results, but it has the disadvantage of returning a reject and not waiting for other promises. So how do you “implement multiple promises that execute synchronously and collect the results regardless of whether an error is thrown?”

This can generally be solved using a for loop, as shown in the following example:

Promise.myAll = function (promiseArr) {
    const len = promiseArr.length;
    const result = [];
    let count = 0;

 return new Promise((resolve, reject) = > {  for (let i = 0; i < len; ++i) {  promiseArr[i].then((res) = > {  result[i] = res;  ++count;   if (count >= len) {  resolve(result);  }  }, (err) => {  result[i] = err;  ++count;   if (count >= len) {  resolve(result);  }  });  }  }); }  // test const fetch = (forceTrue) = > new Promise((resolve, reject) = > {  if (Math.random() > 0.2 || forceTrue) {  resolve('success');  } else {  reject('error');  } });  Promise.myAll([fetch(), fetch(), fetch()])  .then(res= > console.log(res)); // ["success", "success", "error"] Copy the code

But if you notice that both the then and catch methods of a promise return a promise, you can use the following simple method:

Promise.myAll = function (promiseArr) {
    // Just one line of code!
    return Promise.all(promiseArr.map(item= > item.catch(err= > err)));
}

// test const fetch = (forceTrue) = > new Promise((resolve, reject) = > {  if (Math.random() > 0.2 || forceTrue) {  resolve('success');  } else {  reject('error');  } });  Promise.myAll([fetch(), fetch(), fetch()])  .then(res= > console.log(res)); // ["error", "error", "success"] Copy the code