1. 程式人生 > 實用技巧 >promise 實現 async await 原始碼及原理分析

promise 實現 async await 原始碼及原理分析

async-await

原始碼 https://github.com/lfp1024/promise

async-await

const _async = (func) => {
    const p = new Promise((resolve, reject) => {
        try {
            const value = func()
            if (((typeof value === 'object' && value !== null) || typeof value === 'function') &&
                typeof value.then === 'function') {
                Promise.resolve(value).then(resolve, reject)
            } else {
                resolve(value)
            }
        } catch (error) {
            reject(error)
        }
    })
    return p
}

const _await = (() => {
    return (arg) => {
        return (onResolved, onRejected) => {
            const innerPromise = onRejected ? Promise.resolve(arg).catch(onRejected).then(onResolved, onRejected)
                : Promise.resolve(arg).then(onResolved, onRejected)
            return innerPromise
        }
    }
})()


module.exports = {
    _async,
    _await
}

async-await-comment

/* 
async await 是promise的語法糖,優化promise的then鏈寫法,用同步的方式編寫非同步程式碼

async 非同步函式(包含函式宣告、函式表示式、Lambda表示式[箭頭函式]等使用形式)
1. 返回一個 Promise 物件
  1. 直接返回成功或失敗狀態的promise
    1.1 函式體沒有await,return 一個普通值(非promise和thenable物件,預設undefined),async立刻返回一個成功狀態的promise,值為該普通值
    1.2 函式體中沒有await或在await之前,丟擲異常,async立即返回失敗的promise,值為失敗原因,異常不會拋到函式體外面影響外面程式碼的執行
  2. 先返回PENDING狀態的promise,然後再非同步修改狀態
    2.1 函式體中有await,在await獲取到值之前,async先返回 PENDING 狀態的promise,然後再根據await後面表示式返回promise的狀態而改變
    2.2 如果await後面表示式返回的promise失敗且未捕獲異常,則async返回的promise失敗,失敗原因是表示式返回promise的失敗原因
2. 最外層async無法用 await 獲取其返回值,應該用原來的方式:then() 鏈來處理async返回的 promise 物件

await 表示式(包含promise物件,普通函式呼叫、基本值型別)
1. 【等待】表示式的【返回值】
    1.1 如果表示式的值是promise物件,則等待promise返回(呼叫其then方法,非同步獲取),並將其返回值作為await表示式的值
    1.2 如果表示式的值不是promise物件,則通過 Promise.resolve 轉換為 promise物件,等待其返回,並將其返回值作為await表示式的值
2. await相當於呼叫後面表示式返回promise的then方法,非同步(等待)獲取其返回值。即 await<==>promise.then
    2.1 不管程式碼中是否用到await表示式返回值,await都會去獲取(呼叫其then方法),在獲取到之前,async會返回一個 PENDING 狀態的promise。
    2.2 函式體中await表示式後面的程式碼相當於promise.then方法的第一個回撥(onResolved),可以拿到表示式返回promise的返回值(即await表示式返回值)
        因此await會阻塞函式體中後面程式碼的執行(非同步執行then的回撥),但是表示式是同步執行的【因此await操作符只能出現在async非同步函式中】
        如果await表示式後面沒有程式碼,則相當於then的第一個回撥不傳,使用預設回撥函式(v=>v)
    2.3 呼叫promise.then方法的第二個回撥預設不傳,使用預設回撥函式(err=>{throw err})
        因此當表示式報錯或返回失敗的promise,await會將該異常丟擲到函式體中,可以(需要)通過try-catch捕獲異常
        如果await promise呼叫了其catch方法,則不會丟擲,因為catch也返回一個promise,相當於await呼叫catch返回promise的then方法
        第二個回撥傳遞方式:
          1. 當表示式返回值是promise且呼叫其catch方法時,相當於傳遞了第二個回撥(即catch方法中的回撥)
          2. 當await表示式放在try-catch中時,相當於傳遞了第二個回撥(即catch方法中的回撥)
*/

//===================自己實現async、await=====================
const u = require("../utils")
const log = u.debugGenerator(__filename)

/** 
 *@param func: 非同步函式 
 */ 
const _async = (func) => {
    const p = new Promise((resolve, reject) => {
        try {
            const value = func()
            if (((typeof value === 'object' && value !== null) || typeof value === 'function') &&
                typeof value.then === 'function') {
                log.debug("===value is a thenable obj===")
                // promise 或 thenable

                // 1. 如果返回一個thenable物件,這裡需要用Promise.resolve轉為promise,以達到非同步呼叫thenable.then的效果
                // 2. 如果返回一個promise,Promise.resolve原樣返回,無影響。因此統一用Promise.resolve轉為promise
                //    2.1 如果函式體有await,則這裡相當於_await返回的 innerPromise.then(resolve,reject)
                Promise.resolve(value).then(resolve, reject)
                setTimeout(() => {
                    log.info("非同步 async  的 p =", p)
                }, 0);
            } else {
                // 普通值(undefined、123、"123"、{then:123}) 立即返回成功的promise
                resolve(value)
            }
            log.debug("========async return==========")
        } catch (error) {
            log.debug("===value is not a thenable obj===")
            //  3. 如果函式體中同步程式碼報錯,則返回失敗的promise,值為失敗原因
            log.error("========async catch===========\n", error)
            reject(error)
        }
    })
    log.info("同步 async  的 p =", p)
    return p
}


/**
 * @param arg: await後面的表示式
 * @param onResolved: 函式體中await表示式下面的程式碼
 * @param onRejected: 函式體中的catch回撥函式
 */
// 注意變形之後需要加 return _await ...
// 多個await,變形後會巢狀呼叫_await,這裡用計數器n區分
// await promise自帶catch或被try-catch包裹,相當於將catch的回撥函式作為 onRejected 傳入
const _await = (() => {
    let n = 0
    return (arg) => {
        n++
        return (onResolved, onRejected) => {
            // Promise.resolve(arg) 返回失敗,執行 onRejected (如果沒有傳遞則執行then的預設失敗回撥,innerPromise失敗)
            // Promise.resolve(arg) 返回成功,執行 onResolved
            // onResolved 的執行結果決定then返回innerPromise的狀態,從而決定async返回promise的狀態
            // onResolved 拋異常,then內部會捕獲,返回innerPromise失敗,async返回promise失敗
            let innerPromise = onRejected ? Promise.resolve(arg).catch(onRejected).then(onResolved, onRejected)
                : Promise.resolve(arg).then(onResolved, onRejected)
            setTimeout(((n) => {
                return () => {
                    log.info('非同步 then-' + n + ' 的 p =', innerPromise)
                }
            })(n), 0);
            log.info('同步 then-' + n + ' 的 p =', innerPromise)
            return innerPromise
        }
    }
})()

module.exports = {
    _async,
    _await
}



/*
// 傳統promise和async-await編輯器自動轉換

//catch方法轉換為try-catch
function f() {
    return new Promise((res, rej) => {
        setTimeout(() => {
            rej('err')
        }, 1000);
    }).then(data => {
        console.log(data)
    }).catch(err => {
        console.log(err)
    })
}

async function f() {
    try {
        const data = await new Promise((res, rej) => {
            setTimeout(() => {
                rej('err');
            }, 1000);
        });
        console.log(data);
    }
    catch (err) {
        console.log(err);
    }
}

//then的第二個回撥和catch方法都轉換為try-catch

function f() {
    return new Promise((res, rej) => {
        setTimeout(() => {
            rej('err')
        }, 1000);
    }).then(data => {
        console.log(data)
    }, e => {
        console.log(e)
    }).catch(err => {
        console.log(err)
    })
}

async function f() {
    try {
        try {
            const data = await new Promise((res, rej) => {
                setTimeout(() => {
                    rej('err')
                }, 1000)
            })
            console.log(data)
        }
        catch (e) {
            console.log(e)
        }
    }
    catch (err) {
        console.log(err)
    }
}

//多個await

function f() {
    return new Promise((res, rej) => {
        setTimeout(() => {
            rej('err')
        }, 1000);
    }).then(data => {
        return new Promise((res, rej) => {
            res("suc")
        }).then(data => {
            console.log(data)
        })
    }).catch(err => {
        console.log(err)
    })
}

async function f() {
    try {
        const data = await new Promise((res, rej) => {
            setTimeout(() => {
                rej('err')
            }, 1000)
        })
        const data_1 = await new Promise((res, rej) => {
            res("suc")
        })
        console.log(data_1)
    }
    catch (err) {
        console.log(err)
    }
}

*/

測試

await promise.catch

  • await 後面promise自帶catch方法,則失敗或拋異常會被自己的catch捕獲,不影響async函式體中後面程式碼的執行
async function f() {
    console.log("1")
    // await 非同步獲取返回值
    const r = await new Promise((res, rej) => {
        console.log("2")
        rej("1 error")
        console.log("3")
    }).catch(err => {
        console.log('i catch you', err)
        return 123  // catch 捕獲異常,await不丟擲,await表示式的值由catch的返回值決定
    })
    await new Promise((res, rej) => {
        console.log("4",r)
        rej("2 error")
    })
    console.log("res = ", r)
}

console.log("a")
let p = f()
console.log(p)
console.log("b")
setTimeout(() => {
    console.log(p)
}, 0);

// 輸出
// a
// 1
// 2
// 3
// Promise { <pending> }
// b
// i catch you 1 error
// 4 123
// Promise { <rejected> '2 error' }

// 變形
console.log("a")
let p = _async(function f() {
    console.log("1")
    return _await(new Promise((res, rej) => {
        console.log("2")
        rej("1 error")
        console.log("3")
    }))((r) => {
        return _await(new Promise((res, rej) => {
            console.log("4", r)
            rej("2 error")
        }))(() => {
            console.log("res = ", r)
        })
        // catch 回撥作為 onRejected 傳入
    }, (err) => {
        console.log('i catch you', err)
        return 123  
    })
})
console.log(p)
console.log("b")
setTimeout(() => {
    console.log(p)
}, 0);

轉變為自己實現的 _async 和 _await 示意圖