手寫一個async/await的實現


眾所周知,async/await只是一個語法糖,它是基於生成器來實現的,我根據網上的資料,從頭開始寫出它中間的原理實現。

生成器

生成器是在定義函數時在function后添加*定義的,像這樣:function* func(){},執行生成器函數后會得到一個迭代器,在生成器函數中能支持yield來暫停函數,直到迭代器調用next方法.同時next能傳入一個參數來作為yield的值。

這里先定義兩個異步函數來作為以后的實驗對象:

function afunc() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve("afunc");
        }, 1000);
    });
}
function bfunc() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve("bfunc");
        }, 2000);
    });
}

第一版

首先分別定義兩個函數,一個是收集在async函數中需要使用的異步函數,一個是async函數主體在這里運行異步的函數。

function collector(...fn) {
    return function () {
        for (const item of fn) {
            item().then((fulfilled) => it.next(fulfilled));
        }
    };
}
function* async(...fn) {
    let inner = collector(...fn);
    console.log("aaa");
    console.log(yield inner());
    console.log("bbb");
    console.log(yield inner());
}
let it = async(afunc, bfunc);
it.next();

// 運行結果: aaa afunc bbb bfunc

可以看到,已經可以讓同步方法在兩個異步函數中間了,但是這個方案有個問題,本來這個函數期待是三秒打印完,這里只花了兩秒,原因在於在collector中同步進行了數組遍歷,所以需要把。

第二版

既然原因是同步執行了收集到的函數,那我直接寫在函數體里不就行了?這樣就可以得到這個並不好用的第二版:

function* generator() {
    console.log("aaa");
    let result = yield afunc().then((fulfilled) => {
        it.next(fulfilled);
    });
    console.log(result);
    let other = yield bfunc().then((fulfilled) => {
        it.next(fulfilled);
    });
    console.log(other);
}
it = generator();
it.next();

// 運行結果: aaa afunc bbb bfunc

不過既然已經得到了想要的結果,這里只需要把它包裝一下就可以了

第三版

這一版就相當接近async/await的寫法了,通過在生成器函數中定義函數執行順序,在每次執行異步函數時跳出,等到異步函數執行完成后再進行下一步。

function* generator() {
    let result = yield afunc();
    console.log(result);
    let other = yield bfunc();
    console.log(other);
}
myAwait(generator);

function myAwait(genner, ...args) {
    let iter = genner(...args); //得到生成器的迭代器
    return new Promise((resolve, reject) => {
        let result; //iter每次暫停時的結果
        //! inner就是在手動迭代iter
        let inner = function (yield) {
            result = iter.next(yield); //開始迭代 將這里的yield當作yield傳入生成器
            if (result.done) {
                //迭代結束:
                resolve(result.value); //Promise結束
            } else {
                //如果沒有結束 等到promise的結束繼續遞歸
                return Promise.resolve(result.value).then((fulfilled) => {
                    inner(fulfilled);
                });
            }
        };
        inner(); //迭代器第一次不應該傳入參數
    });
}

只要再加上一些錯誤處理,這樣一個手寫async/await就完成了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM