<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>

          初學(xué)者應(yīng)該看的JavaScript Promise 完整指南

          共 10552字,需瀏覽 22分鐘

           ·

          2020-09-11 06:43

          這篇文章算是 JavaScript Promises 比較全面的教程,該文介紹了必要的方法,例如 then,catchfinally。此外,還包括處理更復(fù)雜的情況,例如與Promise.all并行執(zhí)行Promise,通過Promise.race 來處理請求超時(shí)的情況,Promise 鏈以及一些最佳實(shí)踐和常見的陷阱。

          1.JavaScript Promises

          Promise 是一個允許我們處理異步操作的對象,它是 es5 早期回調(diào)的替代方法。

          與回調(diào)相比,Promise 具有許多優(yōu)點(diǎn),例如:

          • 讓異步代碼更易于閱讀。
          • 提供組合錯誤處理。* 更好的流程控制,可以讓異步并行或串行執(zhí)行。

          回調(diào)更容易形成深度嵌套的結(jié)構(gòu)(也稱為回調(diào)地獄)。如下所示:

          a(()?=>?{
          ??b(()?=>?{
          ????c(()?=>?{
          ??????d(()?=>?{
          ????????//?and?so?on?...
          ??????});
          ????});
          ??});
          });

          如果將這些函數(shù)轉(zhuǎn)換為 Promise,則可以將它們鏈接起來以生成更可維護(hù)的代碼。像這樣:

          Promise.resolve()
          ??.then(a)
          ??.then(b)
          ??.then(c)
          ??.then(d)
          ??.catch(console.error);

          在上面的示例中,Promise 對象公開了.then.catch方法,我們稍后將探討這些方法。

          1.1 如何將現(xiàn)有的回調(diào) API 轉(zhuǎn)換為 Promise?

          我們可以使用 Promise 構(gòu)造函數(shù)將回調(diào)轉(zhuǎn)換為 Promise。

          Promise 構(gòu)造函數(shù)接受一個回調(diào),帶有兩個參數(shù)resolvereject。

          • Resolve:是在異步操作完成時(shí)應(yīng)調(diào)用的回調(diào)。
          • Reject:是發(fā)生錯誤時(shí)要調(diào)用的回調(diào)函數(shù)。

          構(gòu)造函數(shù)立即返回一個對象,即 Promise 實(shí)例。當(dāng)在 promise 實(shí)例中使用.then方法時(shí),可以在Promise “完成” 時(shí)得到通知。讓我們來看一個例子。

          Promise 僅僅只是回調(diào)?

          并不是。承諾不僅僅是回調(diào),但它們確實(shí)對.then.catch方法使用了異步回調(diào)。Promise 是回調(diào)之上的抽象,我們可以鏈接多個異步操作并更優(yōu)雅地處理錯誤。來看看它的實(shí)際效果。

          Promise ?反面模式(Promises 地獄)

          a(()?=>?{
          ??b(()?=>?{
          ????c(()?=>?{
          ??????d(()?=>?{
          ????????//?and?so?on?...
          ??????});
          ????});
          ??});
          });

          不要將上面的回調(diào)轉(zhuǎn)成下面的 Promise 形式:

          a().then(()?=>?{
          ??return?b().then(()?=>?{
          ????return?c().then(()?=>?{
          ??????return?d().then(()?=>{
          ????????//????Please?never?ever?do?to?this!???
          ??????});
          ????});
          ??});
          });

          上面的轉(zhuǎn)成,也形成了 Promise 地獄,千萬不要這么轉(zhuǎn)。相反,下面這樣做會好點(diǎn):

          a()
          ??.then(b)
          ??.then(c)
          ??.then(d)

          超時(shí)

          你認(rèn)為以下程序的輸出的是什么?

          const?promise?=?new?Promise((resolve,?reject)?=>?{
          ??setTimeout(()?=>?{
          ????resolve('time?is?up??');
          ??},?1e3);

          ??setTimeout(()?=>?{
          ????reject('Oops??');
          ??},?2e3);
          });

          promise
          ??.then(console.log)
          ??.catch(console.error);

          是輸出:

          time?is?up??
          Oops!??

          還是輸出:

          time?is?up??

          是后者,因?yàn)楫?dāng)一個Promise resolved 后,它就不能再被rejected。

          一旦你調(diào)用一種方法(resolvereject),另一種方法就會失效,因?yàn)?promise 處于穩(wěn)定狀態(tài)。讓我們探索一個 promise 的所有不同狀態(tài)。

          1.2 Promise 狀態(tài)

          Promise 可以分為四個狀態(tài):

          • ? Pending:初始狀態(tài),異步操作仍在進(jìn)行中。
          • ? Fulfilled:操作成功,它調(diào)用.then回調(diào),例如.then(onSuccess)
          • ?? Rejected: 操作失敗,它調(diào)用.catch.then的第二個參數(shù)(如果有)。例如.catch(onError).then(..., onError)。
          • ? Settled:這是 promise 的最終狀態(tài)。promise 已經(jīng)死亡了,沒有別的辦法可以解決或拒絕了。.finally方法被調(diào)用。

          1.3 Promise 實(shí)例方法

          Promise API 公開了三個主要方法:thencatchfinally。我們逐一配合事例探討一下。

          Promise then

          then方法可以讓異步操作成功或失敗時(shí)得到通知。它包含兩個參數(shù),一個用于成功執(zhí)行,另一個則在發(fā)生錯誤時(shí)使用。

          promise.then(onSuccess,?onError);

          你還可以使用catch來處理錯誤:

          promise.then(onSuccess).catch(onError);

          Promise 鏈

          then 返回一個新的 Promise ,這樣就可以將多個Promise 鏈接在一起。就像下面的例子一樣:

          Promise.resolve()
          ??.then(()?=>?console.log('then#1'))
          ??.then(()?=>?console.log('then#2'))
          ??.then(()?=>?console.log('then#3'));

          Promise.resolve立即將Promise 視為成功。因此,以下所有內(nèi)容都將被調(diào)用。輸出將是

          then#1
          then#2
          then#3

          Promise catch

          Promise ?.catch方法將函數(shù)作為參數(shù)處理錯誤。如果沒有出錯,則永遠(yuǎn)不會調(diào)用catch方法。

          假設(shè)我們有以下承諾:1秒后解析或拒絕并打印出它們的字母。

          const?a?=?()?=>?new?Promise((resolve)?=>?setTimeout(()?=>?{?console.log('a'),?resolve()?},?1e3));
          const?b?=?()?=>?new?Promise((resolve)?=>?setTimeout(()?=>?{?console.log('b'),?resolve()?},?1e3));
          const?c?=?()?=>?new?Promise((resolve,?reject)?=>?setTimeout(()?=>?{?console.log('c'),?reject('Oops!')?},?1e3));
          const?d?=?()?=>?new?Promise((resolve)?=>?setTimeout(()?=>?{?console.log('d'),?resolve()?},?1e3));

          請注意,c使用reject('Oops!')模擬了拒絕。

          Promise.resolve()
          ??.then(a)
          ??.then(b)
          ??.then(c)
          ??.then(d)
          ??.catch(console.error)

          輸出如下:


          在這種情況下,可以看到a,bc上的錯誤消息。

          我們可以使用then函數(shù)的第二個參數(shù)來處理錯誤。但是,請注意,catch將不再執(zhí)行。

          Promise.resolve()
          ??.then(a)
          ??.then(b)
          ??.then(c)
          ??.then(d,?()?=>?console.log('c?errored?out?but?no?big?deal'))
          ??.catch(console.error)

          由于我們正在處理 .then(..., onError)部分的錯誤,因此未調(diào)用catchd不會被調(diào)用。如果要忽略錯誤并繼續(xù)執(zhí)行Promise鏈,可以在c上添加一個catch。像這樣:

          Promise.resolve()
          ??.then(a)
          ??.then(b)
          ??.then(()?=>?c().catch(()?=>?console.log('error?ignored')))
          ??.then(d)
          ??.catch(console.error)


          當(dāng)然,這種過早的捕獲錯誤是不太好的,因?yàn)槿菀自谡{(diào)試過程中忽略一些潛在的問題。

          Promise finally

          finally方法只在 Promise 狀態(tài)是 settled 時(shí)才會調(diào)用。

          如果你希望一段代碼即使出現(xiàn)錯誤始終都需要執(zhí)行,那么可以在.catch之后使用.then

          Promise.resolve()
          ??.then(a)
          ??.then(b)
          ??.then(c)
          ??.then(d)
          ??.catch(console.error)
          ??.then(()?=>?console.log('always?called'));

          或者可以使用.finally關(guān)鍵字:

          Promise.resolve()
          ??.then(a)
          ??.then(b)
          ??.then(c)
          ??.then(d)
          ??.catch(console.error)
          ??.finally(()?=>?console.log('always?called'));

          1.4 Promise 類方法

          我們可以直接使用 Promise 對象中四種靜態(tài)方法。

          • Promise.all
          • Promise.reject
          • Promise.resolve
          • Promise.race

          Promise.resolve 和 ?Promise.reject

          這兩個是幫助函數(shù),可以讓 Promise 立即解決或拒絕??梢詡鬟f一個參數(shù),作為下次 .then 的接收:

          Promise.resolve('Yay!!!')
          ??.then(console.log)
          ??.catch(console.error)

          上面會輸出 Yay!!!

          Promise.reject('Oops??')
          ??.then(console.log)
          ??.catch(console.error)

          使用 Promise.all 并行執(zhí)行多個 Promise

          通常,Promise 是一個接一個地依次執(zhí)行的,但是你也可以并行使用它們。

          假設(shè)是從兩個不同的api中輪詢數(shù)據(jù)。如果它們不相關(guān),我們可以使用Promise.all()同時(shí)觸發(fā)這兩個請求。

          在此示例中,主要功能是將美元轉(zhuǎn)換為歐元,我們有兩個獨(dú)立的 API 調(diào)用。一種用于BTC/USD,另一種用于獲得EUR/USD。如你所料,兩個 API 調(diào)用都可以并行調(diào)用。但是,我們需要一種方法來知道何時(shí)同時(shí)完成最終價(jià)格的計(jì)算。我們可以使用Promise.all,它通常在啟動多個異步任務(wù)并發(fā)運(yùn)行并為其結(jié)果創(chuàng)建承諾之后使用,以便人們可以等待所有任務(wù)完成。

          const?axios?=?require('axios');

          const?bitcoinPromise?=?axios.get('https://api.coinpaprika.com/v1/coins/btc-bitcoin/markets');
          const?dollarPromise?=?axios.get('https://api.exchangeratesapi.io/latest?base=USD');
          const?currency?=?'EUR';

          //?Get?the?price?of?bitcoins?on
          Promise.all([bitcoinPromise,?dollarPromise])
          ??.then(([bitcoinMarkets,?dollarExchanges])?=>?{
          ????const?byCoinbaseBtc?=?d?=>?d.exchange_id?===?'coinbase-pro'?&&?d.pair?===?'BTC/USD';
          ????const?coinbaseBtc?=?bitcoinMarkets.data.find(byCoinbaseBtc)
          ????const?coinbaseBtcInUsd?=?coinbaseBtc.quotes.USD.price;
          ????const?rate?=?dollarExchanges.data.rates[currency];
          ????return?rate?*?coinbaseBtcInUsd;
          ??})
          ??.then(price?=>?console.log(`The?Bitcoin?in?${currency}?is?${price.toLocaleString()}`))
          ??.catch(console.log);

          如你所見,Promise.all接受了一系列的 Promises。當(dāng)兩個請求的請求都完成后,我們就可以計(jì)算價(jià)格了。

          我們再舉一個例子:

          const?a?=?()?=>?new?Promise((resolve)?=>?setTimeout(()?=>?resolve('a'),?2000));
          const?b?=?()?=>?new?Promise((resolve)?=>?setTimeout(()?=>?resolve('b'),?1000));
          const?c?=?()?=>?new?Promise((resolve)?=>?setTimeout(()?=>?resolve('c'),?1000));
          const?d?=?()?=>?new?Promise((resolve)?=>?setTimeout(()?=>?resolve('d'),?1000));

          console.time('promise.all');
          Promise.all([a(),?b(),?c(),?d()])
          ??.then(results?=>?console.log(`Done!?${results}`))
          ??.catch(console.error)
          ??.finally(()?=>?console.timeEnd('promise.all'));

          解決這些 Promise 要花多長時(shí)間?5秒?1秒?還是2秒?

          這個留給你們自己驗(yàn)證咯。

          Promise race

          Promise.race(iterable) 方法返回一個 promise,一旦迭代器中的某個promise解決或拒絕,返回的 promise就會解決或拒絕。

          const?a?=?()?=>?new?Promise((resolve)?=>?setTimeout(()?=>?resolve('a'),?2000));
          const?b?=?()?=>?new?Promise((resolve)?=>?setTimeout(()?=>?resolve('b'),?1000));
          const?c?=?()?=>?new?Promise((resolve)?=>?setTimeout(()?=>?resolve('c'),?1000));
          const?d?=?()?=>?new?Promise((resolve)?=>?setTimeout(()?=>?resolve('d'),?1000));

          console.time('promise.race');
          Promise.race([a(),?b(),?c(),?d()])
          ??.then(results?=>?console.log(`Done!?${results}`))
          ??.catch(console.error)
          ??.finally(()?=>?console.timeEnd('promise.race'));

          輸出是什么?

          輸出 b。使用 Promise.race,最先執(zhí)行完成就會結(jié)果最后的返回結(jié)果。

          你可能會問:Promise.race的用途是什么?

          我沒胡經(jīng)常使用它。但是,在某些情況下,它可以派上用場,比如計(jì)時(shí)請求或批量處理請求數(shù)組。

          Promise.race([
          ??fetch('http://slowwly.robertomurray.co.uk/delay/3000/url/https://api.jsonbin.io/b/5d1fb4dd138da811182c69af'),
          ??new?Promise((resolve,?reject)?=>?setTimeout(()?=>?reject(new?Error('request?timeout')),?1000))
          ])
          .then(console.log)
          .catch(console.error);

          如果請求足夠快,那么就會得到請求的結(jié)果。

          1.5 Promise 常見問題

          串行執(zhí)行 promise 并傳遞參數(shù)

          這次,我們將對Node的fs使用promises API,并將兩個文件連接起來:

          const?fs?=?require('fs').promises;?//?requires?node?v8+

          fs.readFile('file.txt',?'utf8')
          ??.then(content1?=>?fs.writeFile('output.txt',?content1))
          ??.then(()?=>?fs.readFile('file2.txt',?'utf8'))
          ??.then(content2?=>?fs.writeFile('output.txt',?content2,?{?flag:?'a+'?}))
          ??.catch(error?=>?console.log(error));

          在此示例中,我們讀取文件1并將其寫入output 文件。稍后,我們讀取文件2并將其再次附加到output文件。如你所見,writeFile promise返回文件的內(nèi)容,你可以在下一個then子句中使用它。

          如何鏈接多個條件承諾?

          你可能想要跳過 Promise 鏈上的特定步驟。有兩種方法可以做到這一點(diǎn)。

          const?a?=?()?=>?new?Promise((resolve)?=>?setTimeout(()?=>?{?console.log('a'),?resolve()?},?1e3));
          const?b?=?()?=>?new?Promise((resolve)?=>?setTimeout(()?=>?{?console.log('b'),?resolve()?},?2e3));
          const?c?=?()?=>?new?Promise((resolve)?=>?setTimeout(()?=>?{?console.log('c'),?resolve()?},?3e3));
          const?d?=?()?=>?new?Promise((resolve)?=>?setTimeout(()?=>?{?console.log('d'),?resolve()?},?4e3));

          const?shouldExecA?=?true;
          const?shouldExecB?=?false;
          const?shouldExecC?=?false;
          const?shouldExecD?=?true;

          Promise.resolve()
          ??.then(()?=>?shouldExecA?&&?a())
          ??.then(()?=>?shouldExecB?&&?b())
          ??.then(()?=>?shouldExecC?&&?c())
          ??.then(()?=>?shouldExecD?&&?d())
          ??.then(()?=>?console.log('done'))

          如果你運(yùn)行該代碼示例,你會注意到只有ad被按預(yù)期執(zhí)行。

          另一種方法是創(chuàng)建一個鏈,然后僅在以下情況下添加它們:

          const?chain?=?Promise.resolve();

          if?(shouldExecA)?chain?=?chain.then(a);
          if?(shouldExecB)?chain?=?chain.then(b);
          if?(shouldExecC)?chain?=?chain.then(c);
          if?(shouldExecD)?chain?=?chain.then(d);

          chain
          ??.then(()?=>?console.log('done'));

          如何限制并行 Promise?

          要做到這一點(diǎn),我們需要以某種方式限制Promise.all。

          假設(shè)你有許多并發(fā)請求要執(zhí)行。如果使用 Promise.all 是不好的(特別是在API受到速率限制時(shí))。因此,我們需要一個方法來限制 Promise 個數(shù), 我們稱其為promiseAllThrottled。

          //?simulate?10?async?tasks?that?takes?5?seconds?to?complete.
          const?requests?=?Array(10)
          ??.fill()
          ??.map((_,?i)?=>?()?=>?new?Promise((resolve?=>?setTimeout(()?=>?{?console.log(`exec'ing?task?#${i}`),?resolve(`task?#${i}`);?},?5000))));

          promiseAllThrottled(requests,?{?concurrency:?3?})
          ??.then(console.log)
          ??.catch(error?=>?console.error('
          Oops?something?went?wrong',?error));

          輸出應(yīng)該是這樣的:


          以上代碼將并發(fā)限制為并行執(zhí)行的3個任務(wù)。

          實(shí)現(xiàn)promiseAllThrottled 一種方法是使用Promise.race來限制給定時(shí)間的活動任務(wù)數(shù)量。

          /**
          ?*?Similar?to?Promise.all?but?a?concurrency?limit
          ?*
          ?*?@param?{Array}?iterable?Array?of?functions?that?returns?a?promise
          ?*?@param?{Object}?concurrency?max?number?of?parallel?promises?running
          ?*/
          function?promiseAllThrottled(iterable,?{?concurrency?=?3?}?=?{})?{
          ??const?promises?=?[];

          ??function?enqueue(current?=?0,?queue?=?[])?{
          ????//?return?if?done
          ????if?(current?===?iterable.length)?{?return?Promise.resolve();?}
          ????//?take?one?promise?from?collection
          ????const?promise?=?iterable[current];
          ????const?activatedPromise?=?promise();
          ????//?add?promise?to?the?final?result?array
          ????promises.push(activatedPromise);
          ????//?add?current?activated?promise?to?queue?and?remove?it?when?done
          ????const?autoRemovePromise?=?activatedPromise.then(()?=>?{
          ??????//?remove?promise?from?the?queue?when?done
          ??????return?queue.splice(queue.indexOf(autoRemovePromise),?1);
          ????});
          ????//?add?promise?to?the?queue
          ????queue.push(autoRemovePromise);

          ????//?if?queue?length?>=?concurrency,?wait?for?one?promise?to?finish?before?adding?more.
          ????const?readyForMore?=?queue.length?????return?readyForMore.then(()?=>?enqueue(current?+?1,?queue));
          ??}

          ??return?enqueue()
          ????.then(()?=>?Promise.all(promises));
          }

          promiseAllThrottled一對一地處理 Promises 。它執(zhí)行Promises并將其添加到隊(duì)列中。如果隊(duì)列小于并發(fā)限制,它將繼續(xù)添加到隊(duì)列中。達(dá)到限制后,我們使用Promise.race等待一個承諾完成,因此可以將其替換為新的承諾。這里的技巧是,promise 自動完成后會自動從隊(duì)列中刪除。另外,我們使用 race 來檢測promise 何時(shí)完成,并添加新的 promise 。


          作者:Adrian Mejia ?譯者:前端小智 ?來源:adrianmjia

          原文:https://adrianmejia.com/promises-tutorial-concurrency-in-javascript-node/


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


          “在看和轉(zhuǎn)發(fā)”就是最大的支持
          瀏覽 58
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

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

          手機(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>
                  青春草视频在线免费观看 | 国产日韩在线欧美视频免费观看 | 四虎成人在线影院 | 99精品人妻无码一区二区三区 | 亚洲精品免费观看 |