前言 Promise Race 方法是咱們在使用 Promise 的時候比較容易使用的一個方法。按照 MDN 對 Promise Race 的定義以下,
The Promise.race(iterable) method returns a promise that resolves or rejects as soon as one of the promises in the iterable resolves or rejects, with the value or reason from that promise.
按照其字面意思理解,Promise.race 方法會返回一個 Promise, 這個 Promise 是一個已經被 resolved 的。 其被 resolved 值爲最快被 resolved 的 Promise 的值或者被 rejected 的值。
換句話說, 就是給予 Promise.race 一個 Promise 數組做爲輸入, 其中最快被 resolved 的值做爲返回值 Promise 的 resolve 或者 rejected 值。
在 MDN 中所貼出來的代碼例子以下:php
var promise1 = new Promise(function(resolve, reject) { setTimeout(resolve, 500, 'one'); }); var promise2 = new Promise(function(resolve, reject) { setTimeout(resolve, 100, 'two'); }); Promise.race([promise1, promise2]).then(function(value) { console.log(value); // Both resolve, but promise2 is faster }); // expected output: "two"
容易形成的誤解 在上面的代碼中,有一句註釋,「Both resolve, but promise2 is faster」, 因此指望的結果是 "two"。這裏會給咱們形成一種錯覺,就是哪一個promise快,就必定返回其 resolve 值。其實在這裏是有一些前提條件的。
Promise.race 必定要儘量在所定義的 Promise 們 以後調用。
在某些狀況下,promise2 就算更快,也不必定返回其值。
下面詳細講一下上面所說的兩種容易形成 Promise.race 錯誤的狀況。
Promise.race 必定要儘量在所定義的 Promise 們 以後調用。
咱們稍稍把 MDN 的代碼作一些改動,讓 Promise.race 不當即執行,而是在下一個執行週期去執行。es6
var promise1 = new Promise(function(resolve, reject) { setTimeout(resolve, 500, 'one'); }); var promise2 = new Promise(function(resolve, reject) { setTimeout(resolve, 100, 'two'); }); // 在這裏,我使用了一個定時器回調 Promise.race 方法。 // 這個定時器的時間正好爲兩個 promise 所要等待時間的最長時間,即500ms。 // 這時, console.log(value)的值只和第一個 promise 相關聯, // 就算 promise2 比 promise1 快,返回的結果仍是 「one」 setTimeout(()=> { Promise.race([promise1, promise2]).then(function(value) { console.log(value); }); }, 500)
在某些狀況下,promise2 就算更快,也不必定返回其值。
咱們再來對 MDN 的代碼作一些調整,以下:npm
var promise1 = new Promise(function(resolve, reject) { setTimeout(resolve, 1, 'one'); }); var promise2 = new Promise(function(resolve, reject) { setTimeout(resolve, 0, 'two'); }); Promise.race([promise1, promise2]).then(function(value) { console.log(value); // Both resolve, but promise2 is faster });
上面的代碼比較極端,可是也能反應一些事情。 promise2 依然更快,可是返回的結果確是 「one」。(這個地方頗有可能和setTimeout的機制有關,我把 promise1 設置爲大於等於2時,返回結果爲「two」。但願有知道的大神補充說明一下。我在之後會繼續研究setTimeout的相關運行機制。)
原罪 爲何會形成上面的錯誤結果呢?咱們能夠先來看看 Promise.race 的實現源代碼。
https://cdn.jsdelivr.net/npm/...數組
function race(entries) { /*jshint validthis:true */ var Constructor = this; // this 是調用 race 的 Promise 構造器函數。 if (!isArray(entries)) { return new Constructor(function (_, reject) { return reject(new TypeError('You must pass an array to race.')); }); } else { return new Constructor(function (resolve, reject) { var length = entries.length; for (var i = 0; i < length; i++) { Constructor.resolve(entries[i]).then(resolve, reject); } }); } }
因此 race 的實現原理就是循環遍歷 [promise1, promise2, ...], 並按照順序去 resolve 各個 promise. 注意:這裏是按照順序遍歷,因此 race 不是嚴格意義的公平 race, 也就是說總有人先搶跑。在這裏 promise1 首先執行其 executor, 而後在調用 race 的時候,又首先被 Promise.race 遍歷。 所以,首先定義的 promise 和放在數組前面的 promise 老是最早具備機會被 resolve。
所以,在這裏,若是咱們沒有考慮到 race 的這種順序遍歷 promise 實例的特性,就有可能獲得意外的結果,正如在上面我所列出的反例所獲得的結果。
第一個列子中,promise1 理論上在500毫秒後 resolve 結果,promise2 理論上在100毫秒後 resolve 結果。我給 Promise.race 設置了一個500毫秒的timer. 這個500毫秒的 timer 給了 promise1 充分的時間去 resolve 結果,因此就算 promise2 resolve 更快,可是獲得的結果仍是 promise1 的結果。
而在第二個例子中,個人理解是,當調用 Promise.race 時,根據上面 race 的源代碼咱們能夠知道,race 會經過給 then 傳遞 resolve 的方式,來把最早完成的 Promise 值給 resolve。 而 then 這種方法是一個異步方法,意思即爲調用 then 之後,不論是 resolve,仍是 reject 都是在下一個週期去執行,因此這就給了一些短時間可以結束的 Promise 機會。這樣,若是 Promise 中的 setTimeout 的時間足夠短的話,那麼在第一次調用 then 時, 前面的 Promise 首先 resolve 掉的話,就算數組後面的 Promise 的 setTimeout 時間更短,那麼也只會 resolve 最早 resolved 的值。
結論 爲了在使用 Promise 不形成非預期結果,咱們須要儘可能在定義完 Promise 們後,當即調用 Promise.race。其實,這一條建議也不是徹底能保證 Promise.race 可以公平地返回最快 resolve 的值,好比:promise
let promises = []; for (let i = 30000; i > -1; i-- ) { promises.push(new Promise(resolve => { setTimeout(resolve, i, i); })) } Promise.race(promises).then(function(value) { console.log(value); // Both resolve, but promise2 is faster });
雖然 Promise.race 在定義完全部 promise 後當即調用,可是因爲 Promise 巨大的數量,超過必定臨界值的話,這時,resolve 出來的值就和遍歷順序以及執行速度有關了。
總之,Promise.race 是順序遍歷,並且經過 then 方法,又把回調函數放入了 event queue 裏, 這樣, 回調函數又要經歷一遍順序調用,若是 event queue 裏的 then 的回調方法都尚未執行完畢的話,那麼 Promise.race 則會返回最快的 resolve 值,可是一旦某些執行較快的異步操做在全部 then 回調函數遍歷完畢以前就獲得了返回結果的話,就有可能形成,異步返回速度雖然快,可是因爲在 event queue 中排在較慢的異步操做以後,獲得一個意外的返回結果的狀況。
轉載於猿2048:➸《Promise Race, 並不公平的 Race》異步