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

掌握 Node.js 中的 async/await

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

容器云強勢上線!快速搭建集群,上萬Linux鏡像隨意使用

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

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

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

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

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

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

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

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

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

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 將其修改得更像在編寫同步代碼:

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

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

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

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

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

通過指數(shù)補償進行重試

使用 Promise 實現(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);
    });

這個代碼看著就頭痛。我們可以使用 async/await 來重寫這段代碼,這樣的代碼會簡單得多。

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);
        }
    }
}

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

中間值

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

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

辦法 1: .then 圣誕樹

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

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

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

辦法 2:移到上層作用域

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

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

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

辦法 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)然是為了使樹扁平化。它們可能是完全不同的類型,所以它們根本不應(yīng)該放在一個數(shù)組中的可能性非常大。

辦法 4:寫一個輔助函數(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)然可以耍點小聰明,寫一個輔助函數(shù)來隱藏上下文,但這樣的代碼會非常難讀,對于那些并不精通函數(shù)式編程技法的人來說,可能不太容易理解。

使用 async/await 就什么問題都沒有了:

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

使用 async/await 處理多個并行請求

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

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

正如我們在這個示例中看到的,我們不需要把這些值移到上層作用域,也不需要創(chuàng)建毫無語義的數(shù)組來傳遞這些值。

數(shù)組迭代方法

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

  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 迭代過程中的返回值,你會看到我們期望的數(shù)組: [ 2, 4, 6, 8 ] 。唯一的問題在于,每個都被 AsyncFunction 封裝成了 Promise。

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

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

本來,你應(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));

這樣看起來簡單一些,不是嗎?

如果在一個長時間運行的異步任務(wù)中,需要迭代一些一些長時間運行的同步邏輯,那么 async/await 仍然會非常有用。

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

filter 呢?很明顯沒對...

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

〔譯者注:譯者饒有興趣的來補充了一個實現(xiàn),貌似不簡單〕

// 譯者補充的實現(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 封裝初始值,返回的積累值也會被封裝,需要等待( await )。

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

.. 因為它的意圖明確地用于命令式編程模式。

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

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

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

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

簡單的 .then 鏈可以直接升級,所以你立刻就能使用 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 重寫 Node.js 應(yīng)用

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

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

你會很愉快的使用 async/await 來改寫服務(wù)。

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

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

你是否已經(jīng)在生產(chǎn)中使用 async/await ,或者準(zhǔn)備堅決不會碰它? 讓我們在下面的評論中討論吧。

 

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

 

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

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

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

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