關於在forEach中使用await的問題

狗子你終於回來了發表於2021-04-06

先說需求,根據陣列中的ID值,對每個ID傳送請求,獲取資料進行操作。

首先肯定考慮用forEach 或者 map對陣列進行遍歷,然後根據值進行操作,但是請求是個非同步操作,forEach又是一個同步操作,等同於同時發出多個非同步請求,並不能確定具體返回的資料是哪個請求。(我這裡的返回資料中有ID值,可以根據ID查詢,這不就是太low了嘛,每次拿到返回來的資料,還得遍歷一遍原陣列才行)

那麼我們如何才能做到即遍歷陣列又傳送請求,同時根據返回值進行操作吶。

解釋幾點:(不想看的人直接跳結果)

async函式:返回一個promise物件,可以使用then方法新增回撥函式。當函式執行的時候,一旦遇到await就會先返回,等到觸發的非同步操作完成,再接著執行函式體後面的語句。
await會獲取promise的成功回撥resolve()返回值,並且只能使用在async函式中(這表面這個函式中有非同步操作)同時await將非同步變成同步,增加了程式碼邏輯可讀性。注意一點,既然是非同步promise就有可能會有rejected的結果,所以一般使用try···catch···或者.catch 來處理請求出錯

結果:
當需要同時發起多個非同步,可以使用promise.all
// const aa = this.testNumArray.map((item) => {
// return this.getFetch(item);
// });

// const bb = Promise.all(aa);
// bb.then((val) => {
// console.log(val, 'bb-result');
// });
這裡的getFetch就是傳送的非同步請求;

如果後一步的非同步請求需要依賴於前一步的資料,即當一個非同步請求完結後拿到資料才可以繼續傳送下一個請求,可以將for of和async/await結合使用
foo = async (array: any) => {
for (let i of array) {
const item = await this.getFetch(i);
console.log(item);
}
};
注意await這裡等到的值,一定是一個promise物件(雖然他也可以處理非promise物件,同樣可以作為resolve()的值進行輸出)

這裡解釋一下,為何forEach是沒有作用的,forEach的第一個引數是一個函式,他的原始碼中並沒有對可遍歷物件的非同步進行處理,所以他是一個即時操作的回撥函式,並不能達到我們非同步的需求。
參考網址https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach#polyfill
原始碼:
// 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

};
}
這裡看不懂沒有關係,記住一點,紅色的程式碼就是直接執行回撥函式。
同時,人家官方都說了:如果使用 promise 或 async 函式作為 forEach() 等類似方法的 callback 引數,最好對造成的執行順序影響多加考慮,否則容易出現錯誤
let ratings = [5, 4, 5];

let sum = 0;

let sumFunction = async function (a, b) {
return a + b;
}

ratings.forEach(async function(rating) {
sum = await sumFunction(sum, rating);
})

console.log(sum);
// Expected output: 14
// Actual output: 0

那麼問題來了,既然forEach不行,那麼for ··· of 為何可以吶?難道for ··· of比較牛逼嘛?
那話如果這麼說,for ··· of真滴比較牛逼,因為for迴圈內建的是一個迭代器,通過Symbol.iterator對可迭代物件的迴圈。

這裡就有一個面試常問到的地方,就是for···of和for···in有什麼區別?
當然是遍歷的值不同了(of:鍵值對,in:key值),還有原理實現上的不同,for···of只能對可迭代物件進行遍歷迴圈。for···of可以視為一個迭代器,那麼他也有迭代器能終止迭代的屬性(break throw continue return)
注意:

  1. 在es6中Object是被認為不可迭代的物件,不能用for···of來迴圈。
  2. for ···in會遍歷所操作物件的所有可列舉屬性,包括原型上的屬性和方法。

相關文章