從ES6開始增加了Promise類型,稱為了主導性的非同步編程機制。 期約Promise是一個有狀態的對象,可能處於如下三種狀態之一: 待定(pending) 兌現(fulfilled,或被稱為“解決”,resolved) 拒絕(rejected) pending是期約的最初始狀態。在這個狀態下,pr ...
從ES6開始增加了Promise類型,稱為了主導性的非同步編程機制。
期約Promise是一個有狀態的對象,可能處於如下三種狀態之一:
- 待定(pending)
- 兌現(fulfilled,或被稱為“解決”,resolved)
- 拒絕(rejected)
pending是期約的最初始狀態。在這個狀態下,promise可以進行落定(settled)為fufilled或rejected,他們分別代表了成功和失敗的狀態。無論落定為哪種狀態都是不可逆的,轉換為resolve或reject後,期約的狀態就不再改變。
promise狀態轉換為兌現resolve時,會有一個私有的內部值(value),而轉換為失敗reject時,會有一個私有的內部理由(reason),預設值為undefined。
由於期約的狀態是私有的,所以改變其狀態只能在內部操作。控制期約狀態轉換是通過調用它的兩個函數參數實現的:resolve()和reject()
為避免期約卡在超時狀態,可以添加一個定時退出功能。比如,可以通過setTImeout設置一個10秒鐘後無論如何都會拒絕期約的回調:
1 let p = new Promise((resolve,reject)=>{ 2 setTimeout(reject,10000);//10秒後調用reject() 3 });
因為期約的狀態碼只能改變一次,所以這裡的超時拒絕邏輯可以放心寫,因為如果在超時執行前進行了狀態的改變,因為其特性,超時無法更改狀態。
promise.resolve()
期約並非一開始就必須處於待定狀態,然後通過resolve()和reject()函數才能轉換為落定狀態。通過調用Promise.resolve()方法可以實例化一個解決的期約。
1 let p1 = new Promise((resolve,reject)=>resolve()); 2 let p2 = new Promise.resolve();
以上兩個期約的實例實際上是一樣的。
傳入promise.resolve()方法的參數對應著value的值
setTimeout(console.log,0,Promise.resolve(3)); //Promise <resolved>:3
promise.resolve()如果傳入的參數本身是一個期約,那它的行為就類似一個空包裝。因此,promise.resolve()可以說是一個冪等方法,冪等性會保留傳入期約的狀態。
let p = new Promise(()=>); setTimeout(console.log,0,p);//Promise<pending> setTimeout(console.log,0,Promise.resolve(p));//Promise<pending> setTimeout(console.log,0,p===Promise.resolve(p));//true 全等 證明理論正確
ps:冪等性的概念如下
冪等性 Idempotent
在數學中的冪等性:f(x)=f(f(x)) ,擁有相同的輸出
Promise.reject()
與promise.resolve()類似,會實例化一個拒絕的期約並拋出一個非同步錯誤(只能通過拒絕處理程式捕獲)。
以下兩個實例實際上是一樣的
let p1 = new Promise(resolve,reject)=>reject()); let p2 = Promise.reject();
拒絕的期約的理由(reason)就是傳給Promise.reject()的第一個參數。這個參數也會傳給後續的拒絕處理程式
Promise.prototype.then()
then()方法是為實例期約添加處理程式的主要方法,最多可接收兩個參數:onResolved和onRejected。這兩個參數是代表成功和拒絕狀態時處理的程式,這兩個參數可以同時選擇,期約會分別在成功和拒絕狀態執行相應的代碼
1 function onResolved(id){ 2 setTimeout(console.log,0,id,'resolved'); 3 } 4 function onRejected(id){ 5 setTimeout(console.log,0,id,'rejected'); 6 } 7 let p1 = new Promise((resolve,reject)=>setTimeout(resolve,3000)); 8 let p2 = new Promise((resolve,reject)=>setTimeout(reject,3000)); 9 p1.then(()=>onResolved('p1'), 10 ()=>onRejected("p1")); 11 p2.then(()=>onResolved('p2'), 12 ()=>onRejected("p2"));
//3000ms after
//p1 resolved
//p2 rejected
如上所述,兩個處理程式參數都可選,且因為期約狀態只能轉換為落定settle狀態一次,所以這兩個操作一定是互斥的。
此外,傳給then()的任何非函數類型的參數都會被靜默忽略。如果只想提供onRejected參數,那就要在onResolved參數的位置上傳入undefined。這樣有助於避免在記憶體中創建多餘的對象,對期待函數參數的類型系統也是一個交代。
//不傳onResolved處理程式的規範寫法 p2.then(null,()=>onRejected('p2'))
Promise.prototype.then()方法返回一個新的期約的實例:
p2.then(null,()=>onRejected('p2')); let p3 = new Promise(()=>{}); let p4 = p3.then(); setTimeout(console.log,0,p3===p4);//false
這個新期約實例基於onResolved 處理程式的返回值構建。換句話說,該處理程式的返回值會通過Promise.resolve()包裝來生成新期約。如果沒有提供這個處理程式,則Promise.resolve()會包裝上一個期約解決之後的值。如果沒有顯式的返回語句,則Promise.resolve()會包裝預設的返回值undefined.
let p1 = Promise.resolve('foo'); let p2 = p1.then(); //若調用then()時不傳處理程式,則原樣向後傳 setTimeout(console.log,0,p2)//Promise<resolved> : foo let p3 = p1.then(()=>undefined); let p4 = p1.then(()=>{}); let p5 = p1.then(()=>Promise.resolve()); setTimeout(console.log,0,p3)//Promise <resolved>:undefined setTimeout(console.log,0,p4)//Promise <resolved>:undefined setTimeout(console.log,0,p5)//Promise <resolved>:undefined
如果有顯式的返回值,則Promise.resolve()會包裝這個值:
1 let p6 = p1.then(()=>'bar'); 2 let p7 = p1.then(()=>Promise.resolve('bar')); 3 4 setTimeout(console.log,0,p6)//Promise <resolved>:bar 5 setTimeout(console.log,0,p7)//Promise <resolved>:bar 6 7 // Promise.resolve()保留返回的期約 8 let p8 = p1.then(()=>new Promise(()=>{})); 9 let p9 = p1.then(()=>Promise.reject()); 10 11 12 setTimeout(console.log,0,p8)//Promise <pending> 13 setTimeout(console.log,0,p9)//Promise <rejected>:undefined
拋出異常會返回拒絕的期約:
1 let p10 = p1.then(()=>{throw 'baz';}); 2 //Uncaught (in promise) baz 3 setTimeout(console.log,0,p10)//Promise <rejected> baz
返回錯誤值不會觸發上面的拒絕行為,而會把錯誤對象包裝在一個解決的期約中:
let p11 = p1.then(()=>Error('qux')); setTimeout(console.log,0,p11)//Promise <resolved>:Error qux
onRejected處理程式也與之類似:onRejected處理程式返回的值也會被Promise.resolve()包裝。拒絕處理程式在捕獲錯誤後不拋出異常是符合期約的行為,應該返回一個解決期約。
Promise.prototype.catch()
該方法用於給期約添加拒絕處理程式。這個方法只接收一個參數:onRejected處理程式。事實上,這個方法就是一個語法糖,調用它就相當於調用Promise.prototype.then(null,onRejected)
語法糖(Syntactic sugar),也譯為糖衣語法,是由英國電腦科學家彼得·約翰·蘭達(Peter J. Landin)發明的一個術語,指電腦語言中添加的某種語法,這種語法對語言的功能並沒有影響,但是更方便程式員使用。通常來說使用語法糖能夠增加程式的可讀性,從而減少程式代碼出錯的機會。
1 let p = Promise.reject(); 2 let onRejected = function(e){ 3 setTimeout(console.log,0,'rejected'); 4 }; 5 6 // 下麵兩種添加拒絕處理程式的方式是一樣的 7 p.then(null,onRejected);//rejected 8 p.catch(onRejected);//rejected
promise.prototype.catch()返回一個新的期約實例:
let p1 = new Promise(()=>{}) let p2 = p1.catch(); setTimeout(console.log,0,p1) setTimeout(console.log,0,p2) setTimeout(console.log,0,p1===p2)
在返回新期約實例方面,Promise.prototype.catch()的行為與Promise.prototype.then()的onRejected處理程式是一樣的。
Promise.prototype.finally()
Promise.prototype.finally()方法用於給期約添加onFinnally處理程式,這個處理程式在期約轉換為解決或拒絕狀態時都會執行。這個方法可以避免onResolved和onRejected處理程式中出現冗餘代碼。但onFinnally處理程式沒有辦法知道期約的狀態是解決還是拒絕,所以這個方法主要用於添加清理代碼。
1 let p1 = Promise.resolve(); 2 let p2 = Promise.reject(); 3 let onFinally=function(){ 4 setTimeout(console.log,0,'Finally!') 5 } 6 7 p1.finally(onFinally)//Finally 8 p2.finally(onFinally)//Finally
promise.prototype.finally()方法返回一個新的期約實例:
1 let p1 = new Promise(()=>{}) 2 let p2 = p1.finally(); 3 setTimeout(console.log,0,p1) //Promise <pending> 4 setTimeout(console.log,0,p2) //Promise <pending> 5 setTimeout(console.log,0,p1===p2) //false
這個新期約不同於then()或catch()方式返回的實例。因為onFinnally被設計為一個狀態無關的方法,所以在大多數情況下它將表現為父期約的傳遞。對於已解決狀態和被拒絕狀態都是如此。
大部分時候,都會原樣後傳。但如果返回的是一個待定的期約,或者onFinnally處理程式拋出了錯誤(顯式拋出或返回了一個拒絕期約),則會返回相應的期約(待定或拒絕),如下所示:
1 let p1 = Promise.resolve('foo') 2 let p2 = p1.finally(()=>new Promise(()=>{})) 3 let p3 = p1.finally(()=>Promise.reject()) 4 5 setTimeout(console.log,0,p2)// promise <pending> 6 setTimeout(console.log,0,p3)// promise <rejected>
返回待定期約的情形並不常見,這是因為只要期約一解決,新期約仍然會原樣後傳初始的期約
如下:
1 let p1 = Promise.resolve('foo') 2 let p2 = p1.finally(()=>new Promise((resolve,reject)=>setTimeout(()=>resolve('bar'),100))); 3 4 setTimeout(console.log,0,p2) 5 6 setTimeout(()=>setTimeout(console.log,0,p2),200)
該代碼會先輸出promise<pending>而在200ms後輸出promise<resolved>:foo,可以明顯的看出由該方法創建的新實例的狀態是由前面的實例狀態後傳的。
非重定入期約的方法
當期約進入落定settle狀態時,與該狀態相關的處理程式僅僅會被排期,而非立即執行。跟在添加這個處理程式的代碼之後的同步代碼一定會在處理程式之前執行。這個特性由JavaScript運行時的保證,被稱為“非重入(non-reentrancy)”特性。下麵的例子演示了這個特性:
1 let p = Promise.resolve() 2 3 p.then(()=>console.log('onResolved handler')) 4 5 console.log('then()returns')
在這個例子中,在一個解決期約上調用then()會把onResolved處理程式推進消息隊列。但這個處理程式在當前線程上的同步代碼執行完全前不會執行。因此,跟在then()後面的同步代碼一定會先於處理程式執行。
先添加處理程式後解決期約也是一樣的。下麵的例子展示了即使先添加了onResolved處理程式,再同步調用resolve(),處理程式也不會進入同步線程執行:
1 let syn; 2 let p = new Promise((resolve) => { 3 syn = function () { 4 console.log('1:invoking resolve()') 5 resolve() 6 console.log('2:resolve() returns') 7 } 8 }) 9 p.then(()=>{ 10 console.log('4:then() handler executes') 11 }) 12 syn() 13 console.log('3:syn() returns');
// 1:...
// 2:...
// 3:...
// 4:...
這個例子中,處理程式作為非同步任務,會在同步任務執行完畢之後,再從消息隊列中 出列執行。
非重入適用於onResolved/onRejected 處理程式、catch()處理程式和finnally()處理程式。
下麵的例子能很明顯的看出:非同步任務總是在同步任務執行完畢後才執行
1 let p1 = Promise.resolve() 2 p1.then(()=>{console.log('p1.then()')}) 3 console.log('p1 over') 4 5 let p2 = Promise.reject() 6 p2.then(null,()=>console.log("p2.then()")) 7 console.log('p2 over') 8 9 let p3 = Promise.reject() 10 p3.catch(()=>console.log("p3.catch()")) 11 console.log('p3 over') 12 13 let p4 = Promise.resolve() 14 p4.finally(()=>console.log('p4.finnally()')) 15 console.log('p4 over') 16 17 //p1 over 18 //p2 over 19 //p3 over 20 //p4 over 21 // p1.then() 22 // p2.then() 23 // p3.catch() 24 // p4.finnally()
鄰近處理程式的執行順序
如果給期約添加了多個處理程式,當期約狀態變化時,相關處理程式會按照添加它們的順序依次執行。無論是then()、catch()、finally()都是一樣的
1 let p1 = Promise.resolve(); 2 let p2 = Promise.reject(); 3 4 p1.then(()=>setTimeout(console.log,0,1)); 5 p1.then(()=>setTimeout(console.log,0,2)); 6 //1 7 //2 8 9 p2.then(null,()=>setTimeout(console.log,0,3)) 10 p2.then(null,()=>setTimeout(console.log,0,4)) 11 //3 12 //4 13 14 p2.catch(()=>setTimeout(console.log,0,5)) 15 p2.catch(()=>setTimeout(console.log,0,6)) 16 //5 17 //6 18 19 p1.finally(()=>setTimeout(console.log,0,7)) 20 p1.finally(()=>setTimeout(console.log,0,8)) 21 //7 22 //8
傳遞解決值和拒絕的理由
到了落定settle狀態後,期約會提供其解決值value(如果成功 )或拒絕理由 reason(如果失敗) 給相關的狀態處理程式。獲取到返回值後,可以進一步對這個值進行操作。比如,第一次網路請求返回的JSON是發送第二次請求必需的數據,那麼第一次請求返回的值就應該傳給onResolved 處理程式繼續處理。當然,失敗的網路請求也應該把HTTP狀態碼傳給onRejected處理程式。
在執行程式中,解決的值和拒絕的理由是分別作為resolve()和reject()的第一個參數往後傳的。然後,這些值又會傳給它們各自的處理程式,作為onResolved或onRejected的參數。下麵的例子展示了傳遞過程:
1 let p1 = new Promise((resolve,reject)=>resolve('foo')) 2 //這裡promise實例p1中調用了resolve方法 並傳遞了參數'foo'給對應的狀態處理程式--作為onResolved的參數 3 p1.then((value)=>{console.log(value)}) 4 //foo 5 6 let p2 = new Promise((resolve,reject)=>reject('baz')) 7 //同上 8 p2.catch((reason)=>console.log(reason)) 9 //baz
當然promise.resolve()和promise.reject()方法也是一樣的
拒絕期約與拒絕錯誤處理
拒絕期約類似於throw()表達式,因為它們都代表一種程式狀態,即需要中斷或者特殊處理。在期約的執行函數或處理程式中拋出錯誤會導致拒絕,對應的錯誤對象會成為拒絕的理由。因此以下這些期約都會以一個錯誤對象為由被拒絕:
1 let p1 = new Promise((resolve,reject)=>reject(Error('foo'))); 2 let p2 = new Promise((resolve,reject)=>{throw Error('foo');}); 3 let p3 = Promise.resolve().then(()=>{throw Error('foo');}); 4 let p4 = Promise.reject(Error('foo')); 5 6 setTimeout(console.log,0,p1) //Promise <rejected>:Error :foo 7 setTimeout(console.log,0,p2) //Promise <rejected>:Error :foo 8 setTimeout(console.log,0,p3) //Promise <rejected>:Error :foo 9 setTimeout(console.log,0,p4) //Promise <rejected>:Error :foo
期約可以以任何理由拒絕,包括undefined,但最好統一使用錯誤對象。這樣做主要是因為創建錯誤對象可以讓瀏覽器捕獲錯誤對象中的棧追蹤信息,而這些信息對於調試是非常關鍵的!例如前面案例中第一個拋出的錯誤的棧追蹤信息:
1 Uncaught (in promise) Error: foo 2 at 04.html:112:55 3 at new Promise (<anonymous>) 4 at 04.html:112:18
所有錯誤都是非同步拋出且未處理的,通過錯誤對象捕獲的棧追蹤信息展示了錯誤發生的路徑。註意錯誤的順序:Promise.resolve().then()的錯誤最後才出現,這是因為它需要在運行時消息隊列中添加處理程式;也就是說,在最終拋出未捕獲錯誤之前它還會創建另一個期約。
這個例子同樣揭示了非同步錯誤一個副作用:正常情況下,在通過throw()關鍵字拋出錯誤時,throw()後面的代碼不會執行,JavaScript的錯誤處理機制會在運行完throw()後暫停執行,但由於非同步錯誤是從消息隊列中非同步拋出的原因,此時不會暫停同步代碼的執行,所以後續的代碼會繼續得到執行。如下:
1 throw Error('foo') 2 console.log('bar') // 這一行不會執行 3 4 Promise.reject(Error('foo')) 5 console.log('continue') //continue成功列印了 說明沒能暫停執行同步任務
非同步錯誤只能通過非同步的onRejected處理程式捕獲:
1 Promise.reject(Error('foo')).catch((e)=>{}); 2 3 //控制台輸入一下判斷是否捕獲正確 4 Promise.reject(Error('foo')).catch((e)=>{console.log(e)});//Error:foo
then()和catch()的onRejected處理程式在語義上相當於try/catch。出發點都是捕獲錯誤之後將其隔離,同時不影響正常邏輯執行。為此,onRejected處理程式任務應該是在捕獲非同步錯誤之後返回一個解決fufilled的期約。下麵的例子中對比了同步錯誤處理與非同步錯誤處理:
1 let a = new Promise((resolve,reject)=>{ 2 console.log('begin') 3 reject(Error('bar')) 4 }).catch((e)=>{ 5 console.log('caught error',e) 6 }).then(()=>{ 7 console.log('continue exe') 8 }) 9 console.log(a)//這裡輸出一下a 得到 promise<fufilled>證明onRejected處理程式任務在捕獲非同步錯誤後 返回了一個fufilled的期約(代表成功捕獲異常?)
期約連鎖與期約合成
多個期約組合在一起可以構成強大的代碼邏輯。這種組合可以通過兩種方式實現:期約連鎖與期約合成。前者就是一個期約接一個期約地拼接,後者則是將多個期約組合為一個期約。
期約連鎖
把期約逐個地串聯起來是一種非常有用的編程模式。之所以可以這樣做,是因為每個期約實例的方法(then()、catch()、finally())都會返回一個新的期約對象,而這個新期約又有自己的實例方法。這樣連綴方法調用就可以構成所謂的“期約連鎖”。例如:
1 let p = new Promise((resolve, reject) => { 2 console.log('first') 3 resolve() 4 }) 5 p.then(() => console.log('second')) 6 .then(() => console.log('third')) 7 .then(() => console.log('fourth')) 8 9 //first 10 //second 11 //third 12 //fourth
這個實現最終執行了一連串同步任務。所以,其實這種方式執行沒有那麼有用。。。直接用多個同步函數也可以做到
如果要真正執行非同步任務,可以改寫前面的例子,讓每個執行器都返回一個期約實例。這樣就可以讓每個後續期約都等待之前的期約,也就是串列化非同步任務。比如,可以像下麵這樣讓每個期約在一定時間後解決:
1 let p = new Promise((resolve, reject) => { 2 console.log('first') 3 setTimeout(resolve, 1000); 4 }); 5 p.then(() => new Promise((resolve, reject) => { 6 console.log('second') 7 setTimeout(resolve, 1000); 8 })) 9 .then(() => new Promise((resolve, reject) => { 10 console.log('third') 11 setTimeout(resolve, 1000); 12 })) 13 .then(() => new Promise((resolve, reject) => { 14 console.log('fourth') 15 setTimeout(resolve,1000) 16 }))
把生成期約的代碼提取到一個工廠模式函數中,如下:
1 function delay(str){ 2 return new Promise((resolve,reject)=>{ 3 console.log(str); 4 setTimeout(resolve,1000); 5 }) 6 } 7 delay('p1 exe') 8 .then(()=>delay('p2 exe')) 9 .then(()=>delay('p3 exe')) 10 .then(()=>delay('p4 exe'))
每個後續的處理程式都會等待前一個期約解決,然後實例化一個新期約並返回它。這種結構可以簡潔地將一部任務串列化,解決之前依賴回調的難題。假如這種情況下不使用期約,那麼前面的代碼可能就要這樣寫了:
1 function delay(str,callback=null){ 2 setTimeout(()=>{ 3 console.log(str) 4 callback && callback(); 5 },1000) 6 } 7 8 delay('p1 callback',()=>{ 9 delay('p2 callback',()=>{ 10 delay('p3 callback',()=>{ 11 delay('p4 callback') 12 }) 13 }) 14 })
這正是期約所要解決的回調地獄問題。
期約圖
因為一個期約可以有任意多個處理程式,所以期約連鎖可以構建有向非迴圈圖的結構。這樣,每個期約都是圖中的一個節點,而使用實例方法添加的處理程式則是有向頂點。因為圖中的每個節點都會等待前一個節點落定,所以圖的方向就是期約的解決或拒絕順序。
下麵的例子展示了一種期約有向圖,也就是二叉樹:
1 let a=new Promise((resolve,reject)=>{ 2 console.log('a'); 3 resolve(); 4 }); 5 6 let b = a.then(()=>console.log('b')) 7 let c = a.then(()=>console.log('c')) 8 9 let d = b.then(()=>console.log('d')) 10 let e = b.then(()=>console.log('e')) 11 let f = c.then(()=>console.log('f')) 12 let g = c.then(()=>console.log('g')) 13 //a 14 //b 15 //c 16 //d 17 //e 18 //f 19 //g
如前所述,期約的處理程式是按照它們的添加的順序執行的。由於期約的處理程式是先添加到消息隊列,然後才逐個執行,因此構成了層序遍歷。
數只是期約圖的一種形式。考慮到根節點不一定唯一,且多個期約也可以組合成一個期約(通過Promise.all()和Promise.race()),所以有向非迴圈圖是體現期約連鎖可能性的最準確表達。
Promise.all()和Promise.race()
Promise類提供兩個將多個期約實例組合成一個期約的靜態方法:Promise.all()和 Promise.race()。而合成後期約的行為取決於內部期約的行為
Promise.all()
Promise.all()靜態方法創建的期約會在一組期約全部解決之後再解決。這個靜態方法接收一個可迭代對象,返回一個新期約:
1 let p1 = Promise.all([ 2 Promise.resolve(), 3 Promise.resolve() 4 ]); 5 6 //可迭代對象中的元素會通過Promise.resolve()轉換為期約 7 let p2 = Promise.all([3,4]) 8 9 //空的刻迭代對象等價於Promise.resolve() 10 let p3 = Promise.all([]) 11 12 //無效 報錯TypeError: cannot read Symbol.iterator of undefined 13 let p4 = Promise.all()
合成的期約只會在每個包含的期約都解決之後解決:
1 let p = Promise.all([ 2 Promise.resolve(), 3 new Promise((resolve,reject)=>setTimeout(resolve,1000)) 4 ]) 5 setTimeout(console.log,0,p)//Promise <pending> 6 7 p.then(()=>setTimeout(console.log,0,'all() resolved')) 8 //1s all() resolved
如果至少有一個包含的期約待定,則合成的期約也會待定。如果有一個包含的期約拒絕,則合成的期約也會拒絕:
1 let p1 = Promise.all([new Promise(()=>{})]) 2 setTimeout(console.log,0,p1)//promise <pending> 3 4 let p2 = Promise.all([ 5 Promise.resolve(), 6 Promise.reject(), 7 Promise.resolve 8 ]) 9 setTimeout(console.log,0,p2)//promise <rejected>
如果所有期約都成功解決,則合成期約的解決值就是所有包含期約解決值的數組,按照迭代器順序排列:
1 let p = Promise.all([ 2 Promise.resolve(0), 3 Promise.resolve(1), 4 Promise.resolve(2) 5 ]) 6 p.then((values)=>setTimeout(console.log,0,values))
如果有期約拒絕,則第一個拒絕的期約會將自己的理由作為合成期約的拒絕理由。之後再拒絕的期約不會影響最終期約的拒絕理由。不過,這並不影響所有包含期約正常的拒絕操作。合成的期約會靜默處理所有包含期約的拒絕操作,如下所示:
1 let p = Promise.all([ 2 Promise.reject(1), 3 Promise.reject(2), 4 Promise.reject(3) 5 ]) 6 p.catch((reason)=>setTimeout(console.log,0,reason));//1
Promise.race()
Promise.race()靜態方法返回一個包裝期約,是一組集合中最先解決或拒絕的期約的鏡像。這個方法接收一個可迭代對象,返回一個新期約:
1 let p1 = Promise.race([ 2 Promise.resolve(), 3 Promise.resolve() 4 ]); 5 6 //可迭代對象中的元素會通過Promise.resolve()轉換為期約 7 let p2 = Promise.race([3,4]) 8 console.log(p2) 9 10 //空的刻迭代對象等價於Promise.resolve() 11 let p3 = Promise.race([]) 12 13 //無效 報錯TypeError: cannot read Symbol.iterator of undefined 14 let p4 = Promise.race() 15
promise.race()不會對解決或拒絕的期約區別對待。無論是解決還是拒絕,只要是第一個落定的期約,promise.race()就會包裝其解決值或拒絕理由並返回新期約:
1 let p1 = Promise.race([ 2 Promise.resolve(3), 3 new Promise((resolve,reject)=>setTimeout(resolve,1000)) 4 ]) 5 setTimeout(console.log,0,p1)// promise<resolved>
如果有一個期約拒絕,只要它是第一個落定的,就會成為拒絕合成期約的理由。之後再拒絕的期約不會影響最終期約的拒絕理由。不過,這並不影響所有包含期約正常的拒絕操作。與Promise.all()類似,合成的期約會靜默處理所有包含期約的拒絕操作,如下所示:
1 let p1 = Promise.race([ 2 Promise.reject(3), 3 new Promise((resolve, reject) => setTimeout(reject, 1000)) 4 ]) 5 p1.catch((reason)=>setTimeout(console.log,0,reason))//3