關於async/await 與forEach 的組合陷阱

今天遇到一個數組排序的坑,是關於ES6的異步語法糖async/await。簡單地記錄下:數組


遇坑

原由是須要在forEach的匿名函數裏await一個異步函數,因而直觀地在匿名函數前加async,簡單例子以下,異步

// 跳序code

function makePromise(num) {
    return new Promise(resolve => {
        resolve(num)
    });
}
[1,2,3,4,5,6].forEach(async (num) => {
    if (num % 2 == 0) {
        let pmnum = await makePromise(num);
        console.log(pmnum)
    } else {
        console.log(num)
    }
});

你們認爲打印順序是什麼?這裏比較容易誤導開發,由於使用await就是爲了讓代碼看起來像同步執行,因此輸出應該會是順序1到6。async

然而答案是——
1,3,5,2,4,6
結果await後面的代碼塊被阻塞了。函數


提問

若是咱們提高async的做用域,使其包含遍歷,this

// 順序code

async function exc() {
    for (let num of [1,2,3,4,5,6]) {
        if (num % 2 == 0) {
            let pmnum = await makePromise(num);
            console.log(pmnum)
        } else {
            console.log(num)
        }
    }
}
exc();

1,2,3,4,5,6
此次await並無阻塞,代碼像是同步執行,二者有什麼區別呢?是什麼致使輸出的順序不同?prototype


解答

先實現本身的forEach函數,線程

Array.prototype.myForEach = function(callback) {
    for (var i = 0; i < this.length; i++)
        callback(this[i], i, this);
};

代替原生的forEach,結果也是跳序1,3,5,2,4,6code

咱們能夠看出跳序和順序code區別在於遍歷裏的callback是否異步,即前者是在遍歷中執行異步方法,後者是在異步方法裏執行遍歷。跳序code等價於下面的代碼段:排序

// 跳序code2

async function exc2(num) {
    if (num % 2 == 0) {
        let pmnum = await makePromise(num);
        console.log(pmnum)
    } else {
        console.log(num)
    }
}
for (let num of [1,2,3,4,5,6]) {
    exc2(num);
}

JavaScript引擎都是單線程執行的,實際上執行到async方法時,不管其方法內部有沒有阻塞,引擎都會繼續往下跑,因此跳過了246。
瞭解ES6 await的機制以後,咱們知道當運行到await時,會阻塞其後面的代碼,但僅限於其所在的async方法內部,簡單來講,此時整個async方法都在等待await的返回,運行環境盞又切換到async方法外部,繼續執行。ip

綜上所述,跳序code2的運行狀況以下

run exc2(1) ↓
run exc2(2) → 遇到await,等待 ↓
run exc2(3) ↓ 
run exc2(4) → 遇到await,等待 ↓
run exc2(5) ↓ 
run exc2(6) → 遇到await,等待 ↓
over

異步返回2 → run exc2(2)餘下的代碼 → over
異步返回4 → run exc2(4)餘下的代碼 → over
異步返回6 → run exc2(6)餘下的代碼 → over

因此,輸出是1,3,5,2,4,6


課後做業

若是exc2不是async方法,輸出的應該是1,2,3,4,5,6你們能夠像上面那樣推算一下順序code的運算順序。

相關文章
相關標籤/搜索