js循環語句中的async與await

NO IMAGE

前言

入冬了,回想起 2016 年的那個寒冬,我遭遇了裁員的風波。給我敲響了警鐘,基礎知識不能忘,時常要複習複習,才能應對一些突如其來的事情,今天篇文章也是因為之前被考到,記憶模糊回答的不是很確定,所以提筆再次記錄一番。

正文

ES7 引入了 async、await 方法,來處理異步操作。關於這倆方法的使用,不贅述,網上的文章多如牛毛,又寫一篇入門,未免廢話連篇。今天要分享的內容是在js的一些循環方法中,async 和 await 的表現是如何的。

for 循環

在 for 循環中,async 和 await 表現的還是令人滿意。現在假設一個場景,設置一個變量,爸媽我三個人的體重,求和

const family = {
"dad": 150,
"mom": 100,
"son": 200
}
const familyToGet = ["dad", "mom", "son"]
// 寫一個sleep方法
const sleep = ms => {
return new Promise(resolve => setTimeout(resolve, ms))
}
// 假設獲取體重數據的過程是一個請求接口的異步過程
const getFamilyWeight = person => {
return sleep(2000).then(() => family[person])
}
const loop_for = async () => {
console.log('start')
let result = 0
for(let i = 0; i < familyToGet.length; i++) {
const person = familyToGet[i]
const weight = await getFamilyWeight(person)
result += weight
}
console.log('result:', result)
console.log('end')
}
loop_for()
// 運行結果
// start
// 150
// 100
// 200
// result: 450
// end

按照上述代碼的打印結果分析,for 循環內的代碼,每一次都會等當前的 getFamilyWeight 方法返回了內容之後,才會繼續下一個循環,循環全部結束的時候,才會跑 for 循環下面的代碼,這是我們想要的結果,nice。

forEach 循環

在 forEach 中,async 和 await 表現的差強人意,具體原因,還應歸結於 forEach 中的回調函數,具體看看代碼的表現。

const family = {
"dad": 150,
"mom": 100,
"son": 200
}
const familyToGet = ["dad", "mom", "son"]
// 寫一個sleep方法
const sleep = ms => {
return new Promise(resolve => setTimeout(resolve, ms))
}
// 假設獲取體重數據的過程是一個請求接口的異步過程
const getFamilyWeight = person => {
return sleep(2000).then(() => family[person])
}
const loop_forEach = () => {
console.log('start')
let result = 0
// 在回調函數中,異步是不好控制的
familyToGet.forEach(async person => {
const num = await getFamilyWeight(person)
console.log(num)
result += num
})
console.log('result', result)
console.log('end')
}
loop_forEach()
// 運行結果
// start
// result 0
// end
// 150
// 100
// 200

從運行結果中,可以看出,代碼沒有等待forEach方法執行結束,而是直接繼續往後執行了。回調函數傳進去之後,2秒鐘之後才會返回數據,這不是我們想要的結果,我現在是希望它能順序的執行。

那麼我們分析一下 forEach 內部大致的原理

Array.prototype.forEach = function(cb) {
// this 為當前調用該函數的變量
for(let i=0; i < this.length; i++) {
cb(this[i], i);
}
}

且看上面的簡易 forEach 重寫,其實內部也是一個 for 循環,那麼為什麼不能表現的和 for 循環一樣的運行結果呢?

我們的回調函數進入 forEach 之後,又被單獨的以 cb() 這種形式執行了一次,相當於在內部又創建了一個 async 、await 形式的方法,當 forEach 函數執行完的時候,相當於創建了 3 個方法,這 3 個方法需要等待2 秒鐘,數據才會返回,而 forEach 外面的代碼是不會等這 3 個函數返回內容再執行,而是一意孤行的管自己走了,所以才會造成這樣的局面。返回問題的根本,我現在的需求是想要代碼能順序執行,且最後能算出一家人體重之和,下面我們來修改一下代碼。

const family = {
"dad": 150,
"mom": 100,
"son": 200
}
const familyToGet = ["dad", "mom", "son"]
// 寫一個sleep方法
const sleep = ms => {
return new Promise(resolve => setTimeout(resolve, ms))
}
// 假設獲取體重數據的過程是一個請求接口的異步過程
const getFamilyWeight = person => {
return sleep(2000).then(() => family[person])
}
const loop_forEach = async () => {
console.log('start')
let promise = []
// 在回調函數中,異步是不好控制的
familyToGet.forEach(person => {
const num = getFamilyWeight(person)
promise.push(num)
})
const result = await Promise.all(promise)
console.log('result', result)
const weight = result.reduce((sum, personWeight) => sum + personWeight)
console.log('weight', weight)
console.log('end')
}
loop_forEach()
// 運行結果
// start
// result [150, 100, 200]
// weight 450
// end

在 forEach 的回調函數內,直接把 getFamilyWeight 方法返回的 promise 對象 push 到 promise 這個數組變量內,通過 Promise.all 來做一層等待的異步處理。

map 遍歷

在 map 中,直接返回一個 promise 數組

const family = {
"dad": 150,
"mom": 100,
"son": 200
}
const familyToGet = ["dad", "mom", "son"]
// 寫一個sleep方法
const sleep = ms => {
return new Promise(resolve => setTimeout(resolve, ms))
}
// 假設獲取體重數據的過程是一個請求接口的異步過程
const getFamilyWeight = person => {
return sleep(2000).then(() => family[person])
}
const loop_map = async () => {
console.log('start')
// map中返回的是一個promise數組,需要通過Promise.all處理
const promise = familyToGet.map(async person => {
const num = await getFamilyWeight(person)
return num;
})
console.log('promise', promise)
// 等待promise裡的內容全部返回,才會繼續往下執行
const result = await Promise.all(promise)
console.log('result', result)
const weight = result.reduce((sum, personWeight) => sum + personWeight)
console.log('weight', weight)
console.log('end')
}
loop_map()
// 運行結果
// start
// promise [Promise, Promise, Promise]
// result [150, 100, 200]
// weight 450
// end

大家應該能感覺到其實這個和上面改版後的 forEach 差不多,沒錯,正如你所料,只不過 map 方法能返回新的數組罷了,forEach 則不能返回數組。

在這裡把 map 的簡單實現寫一下,方便理解

Array.prototype.map = function(cb) {
// this 為當前調用該函數的變量
var o = [];
for(let i=0; i < this.length; i++) {
var temp = cb(this[i], i);
o.push(temp);
}
return o;
}

如上述代碼,在外面通過 o 這個變量收集 cb() 回調函數的返回值,再到外面統一處理。真是妙啊,大家可以細細品味一番,能吾出很多新的對象。

總結

1、for循環內使用async和await還是可以的,穩如老狗🐶

2、不要在forEach方法內使用async、await,儘量避免這種寫法,坑啊。。。

相關文章

排序演化(二):歸併

排序演化(一):希爾

Promises/A+實現,一條規範對應一段代碼

JS類型