1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        async/await ES6 Promise 的最佳實(shí)踐(經(jīng)驗(yàn)分享)

        共 6959字,需瀏覽 14分鐘

         ·

        2020-09-27 04:41


        譯文來(lái)自 https://dev.to/somedood/best-practices-for-es6-promises-36da

        作者?Basti Ortiz (Some Dood)

        ES6 promise 是非常棒的一個(gè)功能, 它是 JavaScript 異步編程中不可或缺的部分,并且取代了以 回調(diào)地獄而聞名的基于回調(diào)的模式。

        然而 promises 的概念并不是非常容易理解。在本文中,我將討論這些年來(lái)學(xué)到的最佳實(shí)踐,這些最佳實(shí)踐可以幫助我充分利用異步 JavaScript。

        處理 promise rejections

        沒(méi)有什么比 unhandled promise rejection(未處理的 promise 錯(cuò)誤) 更讓人頭疼了。當(dāng)一個(gè) promise 拋出一個(gè)錯(cuò)誤,但你沒(méi)有使用Promise#catch來(lái)捕獲程序錯(cuò)誤時(shí),就出現(xiàn)這種情況。

        在調(diào)試高并發(fā)的應(yīng)用程序時(shí),由于錯(cuò)誤信息晦澀難懂(令人頭疼),所以想要找到出錯(cuò)的 promise 是非常困難的。然而,一旦找到出錯(cuò)的 promise 并被認(rèn)為是可復(fù)現(xiàn)的,但是應(yīng)用程序本身的并發(fā)性,應(yīng)用程序的狀態(tài)通常也同樣難以確定??偟膩?lái)說(shuō),這非常的糟糕。

        解決方案很簡(jiǎn)單:雖然你認(rèn)為程序不會(huì)出錯(cuò),但還是要為可能出錯(cuò)的 promises 附加一個(gè) Promise#catch 處理程序。

        此外,在未來(lái)的 Node.js 版本中,未處理的 promise reject 將使 Node 進(jìn)程崩潰。良好的習(xí)慣能夠有效降低出錯(cuò)的概率,現(xiàn)在就是養(yǎng)成良好習(xí)慣的時(shí)機(jī)。

        保持它的"線性"

        https://dev.to/somedood/please-don-t-nest-promises-3o1o

        在之前的一篇文章中,我解釋了避免嵌套 promises 的重要性。簡(jiǎn)而言之,嵌套 promise 又回到了 "回調(diào)地獄 "的模式。promises 的目的是為異步編程提供符合習(xí)慣的標(biāo)準(zhǔn)化語(yǔ)義。如果嵌套 promises,我們又回到了 Node.js api 中流行的冗長(zhǎng)而又相當(dāng)麻煩的錯(cuò)誤優(yōu)先回調(diào)(https://nodejs.org/api/errors.html#errors_error_first_callbacks)。

        Node.js 核心 API 公開(kāi)的大多數(shù)異步方法都遵循慣用模式,稱為錯(cuò)誤優(yōu)先回調(diào)。通過(guò)這種模式,回調(diào)函數(shù)作為參數(shù)傳遞給方法。當(dāng)操作完成或引發(fā)錯(cuò)誤時(shí),將以 Error 對(duì)象(如果有)作為第一個(gè)參數(shù)傳遞來(lái)調(diào)用回調(diào)函數(shù)。如果未引發(fā)錯(cuò)誤,則第一個(gè)參數(shù)將作為 null 傳遞。

        為了保持異步活動(dòng)的“線性”,我們可以使用async 函數(shù)[1]或線性的鏈?zhǔn)?promises。

        import?{?promises?as?fs?}?from?"fs";

        //?嵌套?Promises
        fs.readFile("file.txt").then((text1)?=>
        ??fs.readFile(text1).then((text2)?=>?fs.readFile(text2).then(console.log))
        );

        //?線性鏈?zhǔn)?Promises
        const?readOptions?=?{?encoding:?"utf8"?};
        const?readNextFile?=?(fname)?=>?fs.readFile(fname,?readOptions);
        fs.readFile("file.txt",?readOptions)
        ??.then(readNextFile)
        ??.then(readNextFile)
        ??.then(console.log);

        //?async?函數(shù)
        async?function?readChainOfFiles()?{
        ??const?file1?=?await?readNextFile("file.txt");
        ??const?file2?=?await?readNextFile(file1);
        ??console.log(file2);
        }

        util.promisify 是你最好的伙伴

        當(dāng)我們從錯(cuò)誤優(yōu)先回調(diào)過(guò)渡到 ES6 promises 時(shí),我們習(xí)慣于養(yǎng)成一切 promisifying 化。

        在大多數(shù)情況下,用 Promise 構(gòu)造函數(shù)包裝基于回調(diào)的舊 API 就足夠了。一個(gè)典型的例子是將 `globalThis.setTimeout`[2] 作為sleep函數(shù)

        const?sleep?=?ms?=>?new?Promise(
        ??resolve?=>?setTimeout(resolve,?ms)
        );
        await?sleep(1000);

        但是,其他的外部庫(kù)未必會(huì) "友好 " 地使用的 promises。如果我們不小心,可能會(huì)出現(xiàn)某些不可預(yù)見(jiàn)的副作用--比如內(nèi)存泄漏。在 Node.js 環(huán)境中,util.promisify 函數(shù)的存在就是為了解決這個(gè)問(wèn)題。

        顧名思義,util.promisify可以做兼容和簡(jiǎn)化基于回調(diào)的 API 的包裝。它假定給定函數(shù)像大多數(shù) Node.js API 一樣接受錯(cuò)誤優(yōu)先的回調(diào)作為其最終參數(shù)。如果存在特殊的實(shí)現(xiàn)細(xì)節(jié)[3],則庫(kù)作者還可以提供 自定義 promisifier[4]

        import?{?promisify?}?from?"util";
        const?sleep?=?promisify(setTimeout);
        await?sleep(1000);

        避免順序陷阱

        https://dev.to/somedood/javascript-concurrency-avoiding-the-sequential-trap-7f0

        在本系列的上一篇文章中,我大量討論了調(diào)度多個(gè)獨(dú)立的 Promise 的功能。由于 promise 的順序性,promise 鏈只能使我們走到目前為止。(換句話說(shuō),promise 鏈?zhǔn)街械娜蝿?wù)是按順序執(zhí)行的,譯者注) 因此,讓程序的 "idle time(空閑時(shí)間)" 最小化的關(guān)鍵是并發(fā)。(以下使用 Promise.all 來(lái)實(shí)現(xiàn)并發(fā),譯者注)

        import?{?promisify?}?from?'util';
        const?sleep?=?promisify(setTimeout);

        //?Sequential?Code?(~3.0s)
        sleep(1000)
        ??.then(()?=>?sleep(1000));
        ??.then(()?=>?sleep(1000));

        //?Concurrent?Code?(~1.0s)
        Promise.all([?sleep(1000),?sleep(1000),?sleep(1000)?]);

        注意:promise 也會(huì)阻止事件循環(huán)

        關(guān)于 promise 的最大的誤解可能是一種主觀意識(shí),即 "promises 允許執(zhí)行多線程 的 JavaScript"。盡管事件循環(huán)給出了 并行性(parallelism)的錯(cuò)覺(jué),但這僅是錯(cuò)覺(jué)。在底層,JavaScript 仍然是單線程的。

        事件循環(huán)只允許運(yùn)行時(shí)并發(fā)地進(jìn)行調(diào)度、編排和處理事件。不嚴(yán)格地講,這些“事件”確實(shí)是并行發(fā)生的,但是當(dāng)時(shí)間到了,它們?nèi)詫错樞蛱幚怼?/p>

        在下面的示例中,promise 不會(huì)使用給定的執(zhí)行程序函數(shù)生成新線程。實(shí)際上,執(zhí)行函數(shù)總是在構(gòu)造 promise 時(shí)立即執(zhí)行,從而阻塞事件循環(huán)。執(zhí)行程序函數(shù)返回后,將恢復(fù)頂層執(zhí)行。resolve 的返回值 (Promise#then處理程序的代碼)被延遲到當(dāng)前調(diào)用堆棧完成剩余的頂級(jí)代碼。

        由于 Promise 不會(huì)自動(dòng)產(chǎn)生新線程,因此在后續(xù)Promise#then處理程序中占用大量 CPU 的工作也會(huì)阻塞事件循環(huán)。

        Promise.resolve()
        ??//.then(...)
        ??//.then(...)
        ??.then(()?=>?{
        ????for?(let?i?=?0;?i?1e9;?++i)?continue;
        ??});

        考慮內(nèi)存使用情況

        由于某些不必需的堆分配[5],promises 往往會(huì)占用相對(duì)較高的內(nèi)存和計(jì)算成本。

        除了存儲(chǔ)有關(guān) Promise 實(shí)例本身的信息(例如其屬性和方法)之外,JavaScript 運(yùn)行時(shí)還動(dòng)態(tài)分配更多內(nèi)存以跟蹤與每個(gè) Promise 相關(guān)的異步活動(dòng)。

        此外,考慮到 Promise API 大量使用了閉包和回調(diào)函數(shù)(它們都需要自己的堆分配),令人驚訝的是,一個(gè) promise 就需要大量的內(nèi)存。大量的 promises 可能被證明在熱代碼路徑(hot-code-path )(https://english.stackexchange.com/questions/402436/whats-the-meaning-of-hot-codepath-or-hot-code-path)中。(在熱代碼路徑中分配堆,可能會(huì)觸發(fā)垃圾收集,會(huì)導(dǎo)致性能的極端惡化,因此能少用就好用,譯者注,相關(guān)信息 https://stackoverflow.com/questions/22894877/avoid-allocations-in-compiler-hot-paths-roslyn-coding-conventions)。

        通常來(lái)講,Promise 的每個(gè)新實(shí)例都需要大量堆分配來(lái)存儲(chǔ)屬性,方法,閉包和異步狀態(tài)。我們使用的 promise 越少,從長(zhǎng)遠(yuǎn)來(lái)看,性能會(huì)越好。

        同步的 promise 是不必要且多余的

        像前面所說(shuō),promise 不會(huì)神奇地產(chǎn)生新線程。因此,一個(gè)完全同步的執(zhí)行器函數(shù)(對(duì)于 Promise 構(gòu)造函數(shù))僅僅是一個(gè)不必要的中間層。

        const?promise1?=?new?Promise((resolve)?=>?{
        ??//?Do?some?synchronous?stuff?here...
        ??resolve("Presto");
        });

        類似地,將Promise#then處理程序附加到同步解析的 Promise 只會(huì)稍微延遲代碼的執(zhí)行。對(duì)于此用例,最好使用 global.setImmediate。

        promise1.then((name)?=>?{
        ??//?This?handler?has?been?deferred.?If?this
        ??//?is?intentional,?one?would?be?better?off
        ??//?using?`setImmediate`.
        });

        舉例來(lái)說(shuō),如果執(zhí)行程序函數(shù)不包含異步 I/O 操作,則它僅充當(dāng)不必要的中間層,承擔(dān)不必要的內(nèi)存和計(jì)算開(kāi)銷。

        因此,我個(gè)人不鼓勵(lì)自己在項(xiàng)目中使用Promise.resolvePromise.reject。這些靜態(tài)方法的主要目的是在 promise 中優(yōu)化包裝一個(gè)值。所產(chǎn)生的 promise 將立即得到 resolve,因此可以說(shuō)一開(kāi)始就不需要 promise(除非出于 API 兼容性的考慮)。

        //?Chain?of?Immediately?Settled?Promises
        const?resolveSync?=?Promise.resolve.bind(Promise);
        Promise.resolve("Presto")
        ??.then(resolveSync)?//?Each?invocation?of?`resolveSync`?(which?is?an?alias
        ??.then(resolveSync)?//?for?`Promise.resolve`)?constructs?a?new?promise
        ??.then(resolveSync);?//?in?addition?to?that?returned?by?`Promise#then`.

        長(zhǎng)的 promise 鏈應(yīng)該引起一些注意

        有時(shí)需要串行執(zhí)行多個(gè)異步操作。在這種情況下,promise 鏈?zhǔn)抢硐搿?/p>

        但是,必須注意,由于 Promise API 是可以鏈?zhǔn)秸{(diào)用的,因此每次調(diào)用Promise#then都會(huì)構(gòu)造并返回一個(gè)新的 Promise 實(shí)例(保留了某些先前的狀態(tài))??紤]到中間處理程序會(huì)創(chuàng)建其他 promise,長(zhǎng)鏈有可能對(duì)內(nèi)存和 CPU 使用率造成重大損失。

        const?p1?=?Promise.resolve("Presto");
        const?p2?=?p1.then((x)?=>?x);

        //?The?two?`Promise`?instances?are?different.
        p1?===?p2;?//?false

        換句話說(shuō),所有中間處理程序必須嚴(yán)格地是異步的,也就是說(shuō),它們返回 promises。只有最終處理程序保留運(yùn)行同步代碼的權(quán)利。(最后一個(gè) .then 才配擁有全部同步代碼執(zhí)行的權(quán)利,這樣的方式能夠提高性能,譯者注)

        import?{?promises?as?fs?}?from?"fs";

        //?This?is?**not**?an?optimal?chain?of?promises
        //?based?on?the?criteria?above.
        const?readOptions?=?{?encoding:?"utf8"?};
        fs.readFile("file.txt",?readOptions)
        ??.then((text)?=>?{
        ????//?Intermediate?handlers?must?return?promises.
        ????const?filename?=?`${text}.docx`;
        ????return?fs.readFile(filename,?readOptions);
        ??})
        ??.then((contents)?=>?{
        ????//?This?handler?is?fully?synchronous.?It?does?not
        ????//?schedule?any?asynchronous?operations.?It?simply
        ????//?processes?the?result?of?the?preceding?promise
        ????//?only?to?be?wrapped?(as?a?new?promise)?and?later
        ????//?unwrapped?(by?the?succeeding?handler).
        ????const?parsedInteger?=?parseInt(contents);
        ????return?parsedInteger;
        ??})
        ??.then((parsed)?=>?{
        ????//?Do?some?synchronous?tasks?with?the?parsed?contents...
        ??});

        如上面的示例所示,完全同步的中間處理程序帶來(lái)了對(duì) Promise 的冗余包裝和 resolve 值。這就是為什么我們要遵循最佳 peomise 鏈的策略。為了消除冗余,我們可以簡(jiǎn)單地將有問(wèn)題的中間處理程序的工作集成到后續(xù)處理程序中。

        import?{?promises?as?fs?}?from?"fs";

        const?readOptions?=?{?encoding:?"utf8"?};
        fs.readFile("file.txt",?readOptions)
        ??.then((text)?=>?{
        ????//?Intermediate?handlers?must?return?promises.
        ????const?filename?=?`${text}.docx`;
        ????return?fs.readFile(filename,?readOptions);
        ??})
        ??.then((contents)?=>?{
        ????//?This?no?longer?requires?the?intermediate?handler.
        ????const?parsed?=?parseInt(contents);
        ????//?Do?some?synchronous?tasks?with?the?parsed?contents...
        ??});

        (簡(jiǎn)而言之,promise 鏈能短則短,避免不必要的開(kāi)銷,譯者注。)

        保持簡(jiǎn)單

        如果不需要它們,請(qǐng)不要使用它們。就這么簡(jiǎn)單。

        創(chuàng)建 Promises 的代價(jià)并不是"免費(fèi)"的。它們本身不觸發(fā) JavaScript 中的 "并行性"。(也就是不會(huì)讓代碼執(zhí)行更快,譯者注) 它們只是用于調(diào)度和處理異步操作的標(biāo)準(zhǔn)化抽象。如果我們編寫的代碼不是異步的,那么就不需要 promises。

        然后,通常情況下,我們確實(shí)需要在應(yīng)用程序中使用 promises。這就是為什么我們必須了解所有最佳實(shí)踐,取舍,陷阱和誤區(qū)。當(dāng)然所有的一切,僅僅是最小量使用的問(wèn)題 – 不是因?yàn)?promise 是"惡魔",而是提醒大家不要濫用他們。

        故事未完待續(xù)。在本系列的下一部分中,我將把最佳實(shí)踐的討論擴(kuò)展到 ES2017 異步函數(shù)[6](`async`/`await`)[7].

        參考資料

        [1]

        async 函數(shù): https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function

        [2]

        globalThis.setTimeout: https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout

        [3]

        實(shí)現(xiàn)細(xì)節(jié): https://dev.to/somedood/best-practices-for-es6-promises-36da#fn1

        [4]

        自定義 promisifier: https://nodejs.org/api/util.html#util_custom_promisified_functions

        [5]

        堆分配: https://www.youtube.com/watch?v=wJ1L2nSIV1s

        [6]

        ES2017 異步函數(shù): https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Async_await

        [7]

        (async/await): https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Async_await


        掃碼關(guān)注公眾號(hào),訂閱更多精彩內(nèi)容。



        你點(diǎn)的每個(gè)贊,我都認(rèn)真當(dāng)成了喜歡

        瀏覽 67
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            国产精品 A片在线观看报备 | 在线喷水 | 亚洲精品国产成人AV流浆牛牛 | 久草九九 | 免费婬乱AAA大片 | 免费人成视频在线观看视频 | 日日夜夜一区 | 男女叉叉免费视频 | 嫩草午夜福利电影 | 玖热在线 |