如何使用Promise.race() 和 Promise.any() ?

2020-12-16 51CTO

 

自1996年發布以來,JS 一直在穩步改進。隨著ECMAScript版本的許多改進,最近的版本是ES2020。JS 的一個重要更新是Promise,在2015年,它以 ES6 的名義發布。

什麼是 Promise ?

MDN 上對 Promise 的定義:Promise 對象用於表示一個異步操作的最終完成 (或失敗)及其結果值。對於新手來說,這聽起來可能有點太複雜了。

國外一位大什麼對Promises的解釋如下:「想像一下你是個孩子。你老媽向你保證,她下周會給你買一部新手機。」

你要到下周才能知道你是否能獲取那部手機。你老媽要麼真的給你買了一個全新的手機,要麼因為不開心就不給你買。

這個就是一個Promise。一個Promise有三個狀態。分別是:

  1. Pending:你不知道你是否能得到那部手機
  2. Fulfilled:老媽高興了,給你買了
  3. Rejected:老娘不開森了,不給你買了

這個是我目前聽到,最快能理解 Promise 事例。

如果你還沒有開始學習 Promise ,建議你這樣做。

Promise包含幾種非常有用的內置方法。今天我們主要介紹這兩種方法。

  • Promise.race()-與 ES6 一起發布
  • Promise.any() -仍處於第4階段的提案中

Promise.race()

Promise.race()方法最初是在 ES6 中引入 Promise 時發布的,這個方法需要一個iterable作為參數。

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

與Promise.any()方法不同,Promise.race()方法主要關注 Promise 是否已解決,而不管其被解決還是被拒絕。

語法

  1. Promise.race(iterable) 

參數

iterable — 可迭代對象,類似 Array。iterable 對象實現Symbol.iterator方法。

返回值

一個待定的 Promise 只要給定的迭代中的一個promise解決或拒絕,就採用第一個promise的值作為它的值,從而異步地解析或拒絕(一旦堆棧為空)。

注意

因為參數接受iterable,所以我們可以傳遞一些值,比如基本值,甚至數組中的對象。在這種情況下,race方法將返回傳遞的第一個非 promise 對象。這主要是因為方法的行為是在值可用時(當 promise 滿足時)立即返回值。

此外,如果在iterable中傳遞了已經解決的Promise,則Promise.race()方法將解析為該值的第一個。如果傳遞了一個空的Iterable,則race方法將永遠處於待處理狀態。

事例

  1. const promise1 = new Promise((resolve, reject) => { 
  2.     setTimeout(resolve, 500, 'promise 1 resolved'); 
  3. }); 
  4.  
  5. const promise2 = new Promise((resolve, reject) => { 
  6.     setTimeout(reject, 100, 'promise 2 rejected'); 
  7. }); 
  8.  
  9. const promise3 = new Promise((resolve, reject) => { 
  10.     setTimeout(resolve, 200, 'promise 3 resolved'
  11. }); 
  12.  
  13. (async () => { 
  14.     try { 
  15.         let result = await Promise.race([promise1, promise2, promise3]); 
  16.         console.log(result); 
  17.     } catch (err) { 
  18.         console.error(err); 
  19.     } 
  20. })(); 
  21.   // 輸出- "promise 2 rejected" 
  22.   // 儘管promise1和promise3可以解決,但promise2拒絕的速度比它們快。 
  23.   // 因此Promise.race方法將以promise2拒絕 

真實用例

現在,你可能想知道,我們在實戰中何時 Promise.race() ?來看看。

在請求數據時,顯示加載動畫

使用加載動畫開發中是非常常見。當數據響應時間較長時,如果沒使用加載動畫,看起來就像沒有響應一樣。但有時,響應太快了,我們需要加載動畫時,增加一個非常小延遲時間,這樣會讓用戶覺得我是在經常請求過來的。要實現這一點,只需使用Promise.race()方法,如下所示。

  1. function getUserInfo(user) { 
  2.   return new Promise((resolve, reject) => { 
  3.     // had it at 1500 to be more true-to-life, but 900 is better for testing 
  4.     setTimeout(() => resolve("user data!"), Math.floor(900*Math.random())); 
  5.   }); 
  6.  
  7. function showUserInfo(user) { 
  8.   return getUserInfo().then(info => { 
  9.     console.log("user info:", info); 
  10.     return true
  11.   }); 
  12.  
  13. function showSpinner() { 
  14.   console.log("please wait..."
  15.  
  16. function timeout(delay, result) { 
  17.   return new Promise(resolve => { 
  18.     setTimeout(() => resolve(result), delay); 
  19.   }); 
  20. Promise.race([showUserInfo(), timeout(300)]).then(displayed => { 
  21.   if (!displayed) showSpinner(); 
  22. }); 

**取消的 Promise **

有些情況下,我們需要取消 Promise,這時也可以藉助 Promise.race() 方法:

  1. function timeout(delay) { 
  2.   let cancel; 
  3.   const wait = new Promise(resolve => { 
  4.     const timer = setTimeout(() => resolve(false), delay); 
  5.     cancel = () => { 
  6.       clearTimeout(timer); 
  7.       resolve(true); 
  8.     }; 
  9.   }); 
  10.   wait.cancel = cancel; 
  11.   return wait; 
  12.  
  13.  
  14. function doWork() { 
  15.   const workFactor = Math.floor(600*Math.random()); 
  16.   const work = timeout(workFactor); 
  17.    
  18.   const result = work.then(canceled => { 
  19.     if (canceled) 
  20.       console.log('Work canceled'); 
  21.     else 
  22.       console.log('Work done in', workFactor, 'ms'); 
  23.     return !canceled; 
  24.   }); 
  25.   result.cancel = work.cancel; 
  26.   return result; 
  27.  
  28. function attemptWork() { 
  29.   const work = doWork(); 
  30.   return Promise.race([work, timeout(300)]) 
  31.     .then(done => { 
  32.       if (!done) 
  33.         work.cancel(); 
  34.       return (done ? 'Work complete!' : 'I gave up'); 
  35.   }); 
  36.  
  37. attemptWork().then(console.log); 

批處理請求,用於長時間執行

Chris Jensen 有一個有趣的race()方法用例。他曾使用Promise.race()方法批處理長時間運行的請求。這樣一來,他們可以保持並行請求的數量固定。

  1. const _ = require('lodash'
  2.  
  3. async function batchRequests(options) { 
  4.     let query = { offset: 0, limit: options.limit }; 
  5.  
  6.     do { 
  7.         batch = await model.findAll(query); 
  8.         query.offset += options.limit; 
  9.  
  10.         if (batch.length) { 
  11.             const promise = doLongRequestForBatch(batch).then(() => { 
  12.                 // Once complete, pop this promise from our array 
  13.                 // so that we know we can add another batch in its place 
  14.                 _.remove(promises, p => p === promise); 
  15.             }); 
  16.             promises.push(promise); 
  17.  
  18.             // Once we hit our concurrency limit, wait for at least one promise to 
  19.             // resolve before continuing to batch off requests 
  20.             if (promises.length >= options.concurrentBatches) { 
  21.                 await Promise.race(promises); 
  22.             } 
  23.         } 
  24.     } while (batch.length); 
  25.  
  26.     // Wait for remaining batches to finish 
  27.     return Promise.all(promises); 
  28.  
  29. batchRequests({ limit: 100, concurrentBatches: 5 }); 

Promise.any()

Promise.any() 接收一個Promise可迭代對象,只要其中的一個 promise 成功,就返回那個已經成功的 promise 。如果可迭代對象中沒有一個 promise 成功(即所有的 promises 都失敗/拒絕),就返回一個失敗的 promise 和AggregateError類型的實例,它是 Error 的一個子類,用於把單一的錯誤集合在一起。本質上,這個方法和Promise.all()是相反的。

注意!Promise.any() 方法依然是實驗性的,尚未被所有的瀏覽器完全支持。它當前處於 TC39 第四階段草案(Stage 4)

語法

  1. Promise.any(iterable); 

參數

iterable — 個可迭代的對象, 例如 Array。

返回值

  • 如果傳入的參數是一個空的可迭代對象,則返回一個 已失敗(already rejected) 狀態的 Promise。
  • 如果傳入的參數不包含任何 promise,則返回一個 **異步完成 (asynchronously resolved)**的 Promise。
  • 其他情況下都會返回一個處理中(pending) 的 Promise。只要傳入的迭代對象中的任何一個 promise 變成成功(resolve)狀態,或者其中的所有的 promises 都失敗,那麼返回的 promise 就會 異步地(當調用棧為空時) 變成成功/失敗(resolved/reject)狀態。

說明

這個方法用於返回第一個成功的 promise 。只要有一個 promise 成功此方法就會終止,它不會等待其他的 promise 全部完成。

不像 Promise.all() 會返回一組完成值那樣(resolved values),我們只能得到一個成功值(假設至少有一個 promise 完成)。當我們只需要一個 promise 成功,而不關心是哪一個成功時此方法很有用的。

同時, 也不像 Promise.race() 總是返回第一個結果值(resolved/reject)那樣,這個方法返回的是第一個 成功的 值。這個方法將會忽略掉所有被拒絕的 promise,直到第一個 promise 成功。

事例

  1. const promise1 = new Promise((resolve, reject) => { 
  2.   setTimeout(reject, 100, 'promise 1 rejected'); 
  3. }); 
  4.  
  5. const promise2 = new Promise((resolve, reject) => { 
  6.   setTimeout(resolve, 400, 'promise 2 resolved at 400 ms'); 
  7. }); 
  8.  
  9. const promise3 = new Promise((resolve, reject) => { 
  10.   setTimeout(resolve, 700, 'promise 3 resolved at 800 ms'); 
  11. }); 
  12.  
  13. (async () => { 
  14.   try { 
  15.     let value = await Promise.any([promise1, promise2, promise3]); 
  16.     console.log(value); 
  17.   } catch (error) { 
  18.     console.log(error); 
  19.   } 
  20. })(); 
  21.  
  22. //Output - "promise 2 resolved at 400 ms" 

從上面代碼注意到Promise.any()主要關註解析的值。它會忽略在100毫秒時拒絕的promise1,並考慮在400毫秒後解析的promise2的值。

真實用例

從最快的伺服器檢索資源

假設訪問我們網站的用戶可能來自全球各地。如果我們的伺服器基於單個位置,那麼響應時間將根據每個用戶的位置而不同。但是如果我們有多個伺服器,可以使用能夠產生最快響應的伺服器。在這種情況下,可以使用Promise.any()方法從最快的伺服器接收響應。

我是小智,我們下期再見!

作者:Mahdhi Rezvi 譯者:前端小智 來源: medium

原文:https://blog.bitsrc.io/introduction-to-promise-race-and-promise-any-with-real-life-examples-9d8d1b9f8ec9

本文轉載自微信公眾號「大遷世界」,可以通過以下二維碼關注。轉載本文請聯繫大遷世界公眾號。

【編輯推薦】

【責任編輯:

武曉燕

TEL:(010)68476606】

點讚 0

相關焦點

  • 每日一題之手寫Promise.all和Promise.race
    「手寫源碼題是近兩年面試常見的題目,比如對於Promise來說,可能面試官就會讓你手寫Promise.all或者Promise.race, 你能寫出來嗎
  • Promise 中的三兄弟 .all(), .race(), .allSettled()
    從ES6 開始,我們大都使用的是 Promise.all()和Promise.race(),Promise.allSettled() 提案已經到第4階段,因此將會成為ECMAScript 2020的一部分。
  • promise.race的用法——Vickey前端學習筆記
    於是寫了一段代碼運行一下,就發現,即便第一個promise已成功返回,但是第二個promise的console.error 依然執行了。莫非,Promise.race 理解的有問題?好似說的不是人話,沒看懂。
  • 我終於弄懂了Promise
    寫在前面以前總是似懂非懂,這次總算把它弄了個清楚什麼是PromiseES6 異步編程的一種解決方案,比傳統的方案(回調函數和事件)更加的合理和強大好處 異步操作以同步操作的流程表達出來,避免了層層嵌套的回調函數promise可以解決異步的問題,本身不能說promise是異步的Promise特點對象的狀態不受外界影響
  • ES6之Promise的使用
    Promise構造函數執行時立即調用executor 函數, resolve 和 reject 兩個函數作為參數傳遞給executor(executor 函數在Promise構造函數返回新建對象前被調用)。resolve 和 reject 函數被調用時,分別將promise的狀態改為fulfilled(完成)或rejected(失敗)。
  • 壓箱底筆記:Promise和Async/await的理解和使用
    狀態和指定回調函數誰先誰後?如何串連多個操作任務promise 的 then() 返回一個新的 promise, 可以開成 then() 的鏈式調用,通過 then 的鏈式調用串連多個同步/異步任務。4.2.6 promise 異常傳透當使用 promise 的 then 鏈式調用時, 可以在最後指定失敗的回調,前面任何操作出了異常, 都會傳到最後失敗的回調中處理。
  • JS專題系列之Promise的原理及實現
    三、基於ES6實現AlleyPromisePromise的構造函數必須接受一個函數參數(也就是需要執行異步任務的函數),這個函數傳入後立即調用,該函數有2個參數也就說resolve和reject,如果我們在調用Promise的時候沒有傳入參數則會拋出異常new Promise(); // Uncaught TypeError: Promise resolver undfined
  • 初學者應該看的JavaScript Promise 完整指南
    1.1 如何將現有的回調 API 轉換為 Promise?我們可以使用 Promise 構造函數將回調轉換為 Promise。Promise 構造函數接受一個回調,帶有兩個參數resolve和reject。
  • Promise 初使用
    promise初使用promise是es6是新增的構造器,用來提供另一種異步代碼的實現方案。
  • JavaScript之Promise對象
    javascript是單線程語言,所以都是同步執行的,要實現異步就得通過回調函數的方式,但是過多的回調會導致回調地獄,代碼既不美觀,也不易維護,所以就有了promise;Promise 是異步編程的一種解決方案,比傳統的解決方案——回調函數和事件——更合理和更強大。
  • 淺談NodeJS之Promise
    所以使用Promise的正確場景是這樣的:function runAsync1(){ var promise = new Promise(function(resolve, reject){ setTimeout(function(){
  • 理解異步之美--- Promise與async await(一)
    言歸正傳:寫一個簡單的promise當Promise執行的內容符合你預期的成功條件的話,就調用resolve函數,失敗就調用reject函數,這兩個函數的參數會被promise捕捉到。可以在之後的回調中使用。創建一個承諾我們已經做完了,那麼如何使用承諾後的結果呢?
  • JavaScript 異步與 Promise 實現
    使用promise最基礎的方式是使用它的 then方法,該方法會註冊兩個回調函數,一個接收promise完成的最終值,一個接收promise被拒絕的原因。PROMISES/A你可能還會想問Promises/A是什麼,和Promises/A+有什麼區別。
  • 面試官:你是怎麼理解ES6中 Promise的?使用場景?
    解決異步操作的優點:下面我們正式來認識promise:狀態promise對象僅有三種狀態特點對象的狀態不受外界影響,只有異步操作的結果,可以決定當前是哪一種狀態一旦狀態改變(從pending變為fulfilled和從pending變為rejected),就不會再變,任何時候都可以得到這個結果流程
  • JavaScript異步與Promise實現
    說到底,這樣的回調嵌套,控制權在第三方,對於回調函數的調用方式、時間、次數、順序,回調函數參數,還有下一節將要介紹的異常和錯誤都是不可控的,因為無論如何,並不總能保證第三方是可信任的。使用promise最基礎的方式是使用它的then方法,該方法會註冊兩個回調函數,一個接收promise完成的最終值,一個接收promise被拒絕的原因。PROMISES/A你可能還會想問Promises/A是什麼,和Promises/A+有什麼區別。
  • ES6 Promise實戰練習題
    console.log('promise1', promise1)console.log('promise2', promise2)setTimeout(() => { console.log('promise1', promise1) console.log('promise2', promise2)}, 2000)運行結果:promise1 Promise
  • 前端Javascript進階-ES6中Promise對象
    = new Promise((reslove,reject)=>{}) promise.then((data)=>{ },(err)=>{ })(4)面試題【1】then方法指定的回調函數,將在當前腳本所有同步任務執行完才會執行let promise = new
  • JS的Promise實現原理
    Pending(進行中) 變為 Rejected(已失敗)resolve 和 reject 都可以傳入任意類型的值作為實參,表示 Promise 對象成功(Fulfilled)和失敗(Rejected)的值了解了 Promise 的狀態和值,接下來,我們為 MyPromise 添加狀態屬性和值首先定義三個常量,用於標記Promise對象的三種狀態
  • 簡單記錄下Promise的使用
    ,當我們在需要多個異步請求都執行完再執行下一步操作時,可以考慮使用promise。這裡只簡單記錄下promise的使用。1.簡單的使用:當前案例:有兩個異步請求a和b,a請求返回的結果有b請求需要必須傳的請求參數:a請求成功,進入resolve,b請求執行
  • 你真的懂Promise嗎
    Promise構造函數執行時立即調用executor 函數, resolve 和 reject 兩個函數作為參數傳遞給executor,resolve 和 reject 函數被調用時,分別將promise的狀態改為fulfilled(完成)或rejected(失敗)。一旦狀態改變,就不會再變,任何時候都可以得到這個結果。