Promise期約

来源:https://www.cnblogs.com/cheng-blog/archive/2022/07/25/16511714.html
-Advertisement-
Play Games

從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
              
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 首先,瞭解預解析之前先看兩個問題 1.大家思考下 這個結果會是多少呢? console.log(num); var num=10; 結果是 undefined 2.這個輸出結果又會是多少呢? fun(); var fun=function(){ console.log(22); } 顯然這個結果報錯 ...
  • 條件控制語句及表達式 運算符及表達式 1.()前面不能直接用++ console.log(++(a--)); //() 不能和++ 一起使用 2.str 與 Number值比較(字元串比較時會自動變為Number值) console.log('123A'>213);//false 自動轉為numbe ...
  • 條件控制語句及表達式 運算符及表達式 ()前面不能直接寫++ console.log(++(a));//()不能和++一起使用 字元串和數值進行比較(字元串會自動轉為數值) console.log(1>2)//false console.log('123'>213);//flase 自動轉為numb ...
  • 前段時間在用Promise.all執行一個非常大批量的操作時遇到一個奇怪的問題。 這個Promise.all需要遍歷一個指定目錄中的所有文件,並以非同步的方式讀取文件內容併進行後續操作。由於目錄中的文件數目比較多(大約8000+),Promise.all在執行的過程中有許多文件讀取失敗,但是如果指定一 ...
  • 1 多人語音聊天功能介紹 本文展示瞭如何使用 ZEGO Express SDK 構造多人音視頻通話場景,即實現多對多實時音視頻聊天互動。用戶可在房間內與其餘用戶進行實時音視頻通話,互相推拉流。該場景可用於多人實時音視頻聊天、多人視頻會議等。 2 Web端實現多人語音聊天準備工作 在應用多人音視頻通話 ...
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 微信支付介紹 微信公眾號程式或微信小程式項目中基本都要有微信支付。支付場景基本就是點擊支付,然後手機底部拉起輸入微信支付密碼彈窗,密碼輸入正確後,支付完成。但是無論是微信小程式或微信公眾號程式都是在後端調用微信api介面進行統一下單,將接 ...
  • 需求: el-form 每行顯示兩列,底部按鈕居中 問題: 以前的解決辦法是: el-col, el-row。但是這裡只有一個 el-form-item 的 label 數據是已知的,其餘項都是迴圈得到的,數量不固定,因此不能採用 el-col 方式。 嘗試 嘗試過 item 左浮動,flex,底部 ...
  • 需求 1 :設置初始高亮 子組件: 父組件 需求 2 :高亮行的變化,需要把數據傳遞到兄弟組件中 解決辦法:EventBus 參考鏈接: http://t.csdn.cn/iwOJc main.js 中: 子組件 1 : 子組件 2 : 需求 3 :子組件把高亮行的 index 和表格總條數傳遞給父 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...