中文字幕在线观看,亚洲а∨天堂久久精品9966,亚洲成a人片在线观看你懂的,亚洲av成人片无码网站,亚洲国产精品无码久久久五月天

掌握 Node.js 中的 async/await

2018-07-20    來(lái)源:編程學(xué)習(xí)網(wǎng)

容器云強(qiáng)勢(shì)上線!快速搭建集群,上萬(wàn)Linux鏡像隨意使用

你會(huì)在本文中學(xué)到如何使用 async 函數(shù)(async/await) 來(lái)簡(jiǎn)化回調(diào),以及基于 Promise 的 Node.js 應(yīng)用。

異步語(yǔ)言結(jié)構(gòu)已經(jīng)在其它語(yǔ)言中存在好一陣了,比如 C# 的 async/await,Kotlin 的協(xié)程(Coroutine) 以及 Go 中的 Goroutine。隨著 Node.js 8 的發(fā)布,期待已久的異步函數(shù)功能終于來(lái)臨。

在本教程結(jié)束的時(shí)候,你應(yīng)該可以回答下面的問(wèn)題:

Node.js 中的 async/await 是自發(fā)明面包切片以來(lái)最美好的事情嗎?

Node 中的異步函數(shù)是什么鬼?

異步函數(shù)聲明返回 AsyncFunction 對(duì)象。這在某種意義上來(lái)說(shuō)與 Generator 相似——它們的執(zhí)行可以被中止。唯一的不同之處在于他們異步函數(shù)總是返回 Promise 而不是 { value: any, done: Boolean } 對(duì)象。實(shí)際上,異步函數(shù)與你從 co 包中體驗(yàn)到的功能非常相似。

在異步函數(shù)中你可以等待(await) 任何 Promise 或捕獲其拒絕(reject) 的原因。

因此,如果你有像下面這們用 Promise 實(shí)現(xiàn)的邏輯:

〔譯者注:文本中的代碼都根據(jù)譯者推薦的格式進(jìn)行了格式化,這不影響代碼的意義〕

function handler(req, res) {
    return request("https://user-handler-service")
        .catch((err) => {
            logger.error("Http error", err);
            error.logged = true;
            throw err;
        })
        .then((response) => Mongo.findOne({ user: response.body.user }))
        .catch((err) => {
            !error.logged && logger.error("Mongo error", err);
            error.logged = true;
            throw err;
        })
        .then((document) => executeLogic(req, res, document))
        .catch((err) => {
            !error.logged && console.error(err);
            res.status(500).send();
        });
}

你可以使用 async/await 將其修改得更像在編寫(xiě)同步代碼:

async function handler(req, res) {
    let response;
    try {
        response = await request("https://user-handler-service");
    } catch (err) {
        logger.error("Http error", err);
        return res.status(500).send();
    }

    let document;
    try {
        document = await Mongo.findOne({ user: response.body.user });
    } catch (err) {
        logger.error("Mongo error", err);
        return res.status(500).send();
    }

    executeLogic(document, req, res);
}

在較舊的 V8 中,如果沒(méi)有處理拒絕的 Promise,它只是悄悄地被丟棄了。現(xiàn)在至少你會(huì)從 Node 中得到一個(gè)警告,因此你不必為此擔(dān)心而去創(chuàng)建一個(gè)監(jiān)聽(tīng)程序。然而,如果你不能處理錯(cuò)誤而讓?xiě)?yīng)用處于一個(gè)未知的狀態(tài),那么推薦的做法是讓你的應(yīng)用崩潰掉:

process.on("unhandledRejection", (err) => {
    console.error(err);
    process.exit(1);
});

使用異步函數(shù)的模式

確實(shí)有一些案例,如果能像寫(xiě)同步程序一樣方便的進(jìn)行異步操作就好了。使用 Promise 和回調(diào)來(lái)處理它們會(huì)需要復(fù)雜的模式或引用其它庫(kù)。

這里有一些例子,需要在循環(huán)中異步獲取數(shù)據(jù),或使用 if-else 條件。

通過(guò)指數(shù)補(bǔ)償進(jìn)行重試

使用 Promise 實(shí)現(xiàn)重試邏輯非常笨拙:

function requestWithRetry(url, retryCount) {
    if (retryCount) {
        return new Promise((resolve, reject) => {
            const timeout = Math.pow(2, retryCount);

            setTimeout(() => {
                console.log("Waiting", timeout, "ms");
                _requestWithRetry(url, retryCount)
                    .then(resolve)
                    .catch(reject);
            }, timeout);
        });
    } else {
        return _requestWithRetry(url, 0);
    }
}

function _requestWithRetry(url, retryCount) {
    return request(url, retryCount)
        .catch((err) => {
            if (err.statusCode && err.statusCode >= 500) {
                console.log("Retrying", err.message, retryCount);
                return requestWithRetry(url, ++retryCount);
            }
            throw err;
        });
}

requestWithRetry("http://localhost:3000")
    .then((res) => {
        console.log(res);
    })
    .catch(err => {
        console.error(err);
    });

這個(gè)代碼看著就頭痛。我們可以使用 async/await 來(lái)重寫(xiě)這段代碼,這樣的代碼會(huì)簡(jiǎn)單得多。

function wait(timeout) {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve();
        }, timeout);
    });
}

async function requestWithRetry(url) {
    const MAX_RETRIES = 10;
    for (let i = 0; i <= MAX_RETRIES; i++) {
        try {
            return await request(url);
        } catch (err) {
            const timeout = Math.pow(2, i);
            console.log("Waiting", timeout, "ms");
            await wait(timeout);
            console.log("Retrying", err.message, i);
        }
    }
}

這樣的代碼更讓人賞心悅目,不是嗎?

中間值

這個(gè)例子不像之前的例子那么可怕,但是如果你有 3 個(gè)異步函數(shù)在順序上存在依賴關(guān)系,你恐怕不得不從幾個(gè)不太好看的解決辦法中選擇一個(gè)。

functionA 返回 Promise,而調(diào)用 functionB 需要那個(gè)值,然后 functionC 需要從 functionA 和 functionB 的 Promise 中帶回來(lái)的值。

辦法 1: .then 圣誕樹(shù)

function executeAsyncTask() {
    return functionA()
        .then((valueA) => {
            return functionB(valueA)
                .then((valueB) => {
                    return functionC(valueA, valueB);
                });
        });
}

在這個(gè)辦法中,我們通過(guò) 3 層代碼獲得 valueA ,同時(shí)從上一個(gè) Promise 獲得 valueB 。我們不能讓這個(gè)圣誕樹(shù)扁平化,否則就不會(huì)形成閉包,在調(diào)用 functionC 的時(shí)候就拿不到 valueA 。

〔譯者注:翻譯過(guò)程中去掉了一些疑似廣告的鏈接〕

辦法 2:移到上層作用域

function executeAsyncTask() {
    let valueA;
    return functionA()
        .then((v) => {
            valueA = v;
            return functionB(valueA);
        })
        .then((valueB) => {
            return functionC(valueA, valueB);
        });
}

在圣誕樹(shù)中,我們使用了更高導(dǎo)的作用域使 valueA 有效。這里的情況類似,只不過(guò)我們現(xiàn)在把 valueA 定義在所有 .then 之外,然后我們可以將第一個(gè) Promise 的確定值賦給它。

這個(gè)辦法當(dāng)然有效,即扁平化了 .then 鏈又保持了正確的語(yǔ)義。然而,它也帶來(lái)了新的缺陷,比如 valueA 會(huì)在函數(shù)其它地方使用。我們需要使用兩個(gè)變量 —— valueA 和 v —— 它們是同一個(gè)值。

辦法 3:不必要的數(shù)組

function executeAsyncTask() {
    return functionA()
        .then(valueA => {
            return Promise.all([valueA, functionB(valueA)]);
        })
        .then(([valueA, valueB]) => {
            return functionC(valueA, valueB);
        });
}

把 valueA 與 functionB 產(chǎn)生的 Promise 一起放在數(shù)組中,當(dāng)然是為了使樹(shù)扁平化。它們可能是完全不同的類型,所以它們根本不應(yīng)該放在一個(gè)數(shù)組中的可能性非常大。

辦法 4:寫(xiě)一個(gè)輔助函數(shù)

const converge = (...promises) => (...args) => {
    let [head, ...tail] = promises;
    if (tail.length) {
        return head(...args)
            .then((value) => converge(...tail)(...args.concat([value])));
    } else {
        return head(...args);
    }
};

functionA(2)
    .then((valueA) => converge(functionB, functionC)(valueA));

你當(dāng)然可以耍點(diǎn)小聰明,寫(xiě)一個(gè)輔助函數(shù)來(lái)隱藏上下文,但這樣的代碼會(huì)非常難讀,對(duì)于那些并不精通函數(shù)式編程技法的人來(lái)說(shuō),可能不太容易理解。

使用 async/await 就什么問(wèn)題都沒(méi)有了:

async function executeAsyncTask() {
    const valueA = await functionA();
    const valueB = await functionB(valueA);
    return function3(valueA, valueB);
}

使用 async/await 處理多個(gè)并行請(qǐng)求

這與前面的例子相似。這里你是想同時(shí)執(zhí)行幾個(gè)異步任務(wù),并不同的地方使用它們的結(jié)果值,使用 async/await 很容易解決:

async function executeParallelAsyncTasks() {
    const [valueA, valueB, valueC]
        = await Promise.all([
            functionA(),
            functionB(),
            functionC()
        ]);
    doSomethingWith(valueA);
    doSomethingElseWith(valueB);
    doAnotherThingWith(valueC);
}

正如我們?cè)谶@個(gè)示例中看到的,我們不需要把這些值移到上層作用域,也不需要?jiǎng)?chuàng)建毫無(wú)語(yǔ)義的數(shù)組來(lái)傳遞這些值。

數(shù)組迭代方法

你可以結(jié)合異步函數(shù)使用 map 、 filter 和 reduce ,不過(guò)它們的行為并不直觀。猜猜下面的腳本會(huì)在控制臺(tái)打印出什么:

  1. map
function asyncThing(value) {
    return new Promise((resolve, reject) => {
        setTimeout(() => resolve(value), 100);
    });
}

async function main() {
    return [1, 2, 3, 4].map(async (value) => {
        const v = await asyncThing(value);
        return v * 2;
    });
}

main()
    .then(v => console.log(v))
    .catch(err => console.error(err));
  1. filter
function asyncThing(value) {
    return new Promise((resolve, reject) => {
        setTimeout(() => resolve(value), 100);
    });
}

async function main() {
    return [1, 2, 3, 4].filter(async (value) => {
        const v = await asyncThing(value);
        return v % 2 === 0;
    });
}

main()
    .then(v => console.log(v))
    .catch(err => console.error(err));
  1. reduce
function asyncThing(value) {
    return new Promise((resolve, reject) => {
        setTimeout(() => resolve(value), 100);
    });
}

async function main() {
    return [1, 2, 3, 4].reduce(async (acc, value) => {
        return await acc + await asyncThing(value);
    }, Promise.resolve(0));
}

main()
    .then(v => console.log(v))
    .catch(err => console.error(err));

答案:

[ Promise { }, Promise { }, Promise { }, Promise { } ]
[ 1, 2, 3, 4 ]
10

如果你記錄 map 迭代過(guò)程中的返回值,你會(huì)看到我們期望的數(shù)組: [ 2, 4, 6, 8 ] 。唯一的問(wèn)題在于,每個(gè)都被 AsyncFunction 封裝成了 Promise。

因此,如果你想得到正確的值,就需要用 Promise.all 對(duì)返回的數(shù)組進(jìn)行解封。

main()
    .then(v => Promise.all(v))
    .then(v => console.log(v))
    .catch(err => console.error(err));

本來(lái),你應(yīng)該先等待 Promise 確定值,然后映射值:

function main() {
    return Promise.all([1, 2, 3, 4]
        .map((value) => asyncThing(value)));
}

main()
    .then(values => values.map((value) => value * 2))
    .then(v => console.log(v))
    .catch(err => console.error(err));

這樣看起來(lái)簡(jiǎn)單一些,不是嗎?

如果在一個(gè)長(zhǎng)時(shí)間運(yùn)行的異步任務(wù)中,需要迭代一些一些長(zhǎng)時(shí)間運(yùn)行的同步邏輯,那么 async/await 仍然會(huì)非常有用。

這樣一來(lái),只要有一個(gè)值你就可以開(kāi)始計(jì)算 —— 不必等到所有 Promise 都解決了才開(kāi)始計(jì)算。即使結(jié)果仍然被封裝在 Promise 中,它們?nèi)匀槐软樞驁?zhí)行快得多。

filter 呢?很明顯沒(méi)對(duì)...

很好,你猜到了:雖然返回值是 [ false, true, false, true ] ,但它們被封裝成 Promise,所以會(huì)從原來(lái)的數(shù)組取回所有值〔譯者注:因?yàn)?Promise 對(duì)象會(huì)被判為 true 〕。很不幸,要修正這個(gè)錯(cuò)誤,就需要要確定所有值,然后再過(guò)濾。

〔譯者注:譯者饒有興趣的來(lái)補(bǔ)充了一個(gè)實(shí)現(xiàn),貌似不簡(jiǎn)單〕

// 譯者補(bǔ)充的實(shí)現(xiàn) (只修改了 main 函數(shù))
async function main() {
    const promises = [1, 2, 3, 4]
        .map(async value => {
            const v = await asyncThing(value);
            return {
                value: value,
                predicate: v % 2 === 0
            };
        });

    return (await Promise.all(promises))
        .filter(m => m.predicate)
        .map(m => m.value);
}

歸約(reduce)相當(dāng)直接。但要記住,你需要用 Promise.resolve 封裝初始值,返回的積累值也會(huì)被封裝,需要等待( await )。

如果你希望使用函數(shù)式編程模式, async/await 可能不適合你。

.. 因?yàn)樗囊鈭D明確地用于命令式編程模式。

為了讓 .then 鏈看起來(lái)更純粹,你可以使用 Ramda 的 pipeP 和 composeP 函數(shù).

重寫(xiě)基于回調(diào)的 Node.js 應(yīng)用程序

異步函數(shù)默認(rèn)返回 Promise ,所以你可以重寫(xiě)基于回調(diào)的函數(shù),讓它們使用 Promise,然后等待( await ) 解決。你可以使用 Node.js 的 util.promisify 函數(shù)將基于回調(diào)的函數(shù)轉(zhuǎn)換為基于 Promise 的函數(shù)。

重寫(xiě)基于 Promise 的應(yīng)用程序

簡(jiǎn)單的 .then 鏈可以直接升級(jí),所以你立刻就能使用 async/await 。

function asyncTask() {
    return functionA()
        .then((valueA) => functionB(valueA))
        .then((valueB) => functionC(valueB))
        .then((valueC) => functionD(valueC))
        .catch((err) => logger.error(err));
}

可以改為

async function asyncTask() {
    try {
        const valueA = await functionA();
        const valueB = await functionB(valueA);
        const valueC = await functionC(valueB);
        return await functionD(valueC);
    } catch (err) {
        logger.error(err);
    }
}

使用 async/await 重寫(xiě) Node.js 應(yīng)用

  • 如果你喜歡經(jīng)典的 if-else 條件和 for/while 循環(huán),

  • 如果你認(rèn)同 try-catch 塊處理錯(cuò)誤的方式,

你會(huì)很愉快的使用 async/await 來(lái)改寫(xiě)服務(wù)。

正如我們看到的那樣,它可以讓某些模式更容易編寫(xiě)也更容易閱讀,所以它肯定在某些情況下比 Promise.then() 鏈更合適。然而,如果你陷入了過(guò)去幾年的函數(shù)式編程熱,你可能想忽略這一語(yǔ)言特性。

那么你們都在想什么呢? async/await 是發(fā)明面包切片之后最好的事情,還是像 es2015 的 class 一樣有爭(zhēng)議呢?

你是否已經(jīng)在生產(chǎn)中使用 async/await ,或者準(zhǔn)備堅(jiān)決不會(huì)碰它? 讓我們?cè)谙旅娴脑u(píng)論中討論吧。

 

來(lái)自:http://www.zcfy.cc/article/mastering-async-await-in-node-js-risingstack-4102.html

 

標(biāo)簽: 代碼 腳本

版權(quán)申明:本站文章部分自網(wǎng)絡(luò),如有侵權(quán),請(qǐng)聯(lián)系:west999com@outlook.com
特別注意:本站所有轉(zhuǎn)載文章言論不代表本站觀點(diǎn)!
本站所提供的圖片等素材,版權(quán)歸原作者所有,如需使用,請(qǐng)與原作者聯(lián)系。

上一篇:幾段 Python 代碼理解面向?qū)ο?/a>

下一篇:iOS中實(shí)現(xiàn)一個(gè)支持小數(shù)的星星評(píng)分組件