<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          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 公開的大多數(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ù)見的副作用--比如內(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ò)覺,但這僅是錯(cuò)覺。在底層,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ì)算開銷。

          因此,我個(gè)人不鼓勵(lì)自己在項(xiàng)目中使用Promise.resolvePromise.reject。這些靜態(tài)方法的主要目的是在 promise 中優(yōu)化包裝一個(gè)值。所產(chǎn)生的 promise 將立即得到 resolve,因此可以說(shuō)一開始就不需要 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 鏈能短則短,避免不必要的開銷,譯者注。)

          保持簡(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)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  天堂在线视频 | 久久久久久久久成人电影 | 永久免费一区二区三区 | 欧美熟妇xxxxxx | 色老板在线永久免费网站 |