記錄學習Promise

mylittleZ發表於2019-03-15

同步與非同步

JavaScript的執行環境是「單執行緒」,同步模式,即上述所說的單執行緒模式,一次只能執行一個任務,非同步模式,可以一起執行多個任務,函式呼叫後不會立即返回執行的結果,如果任務A需要等待,可先執行任務B,等到任務A結果返回後再繼續回撥。

回撥函式

常見的非同步回撥即setTimeout,ajax請求。可理解為(執行完)回(來)調(用)的函式。

Promise

Promise物件代表一個未完成、但預計將來會完成的操作。 它有以下三種狀態: pending:初始值,不是fulfilled,也不是rejected fulfilled:代表操作成功 rejected:代表操作失敗 Promise有兩種狀態改變的方式,既可以從pending轉變為fulfilled,也可以從pending轉變為rejected。一旦狀態改變,就不能再變了,會一直保持這個狀態。當狀態發生變化,promise.then繫結的函式就會被呼叫。 注意:Promise一旦新建就會「立即執行」,無法取消。這也是它的缺點之一。

下面來看一個最簡單的promise栗子

var promise = new Promise(function (resolve, reject) {
    if (/* 非同步操作成功 */) {
        resolve(data);
    } else {
        /* 非同步操作失敗 */
        reject(error);
    }
});
複製程式碼

Promise接受一個「函式」作為引數,該函式的兩個引數分別是resolve和reject。這兩個函式就是就是「回撥函式」,由JavaScript引擎提供。

resolve函式的作用:在非同步操作成功時呼叫,並將非同步操作的結果,作為引數傳遞出去;

reject函式的作用:在非同步操作失敗時呼叫,並將非同步操作報出的錯誤,作為引數傳遞出去。

瞭解完這些基礎知識後,可以直接通過程式碼來深入瞭解了。

下面就是很多栗子...

例一:Promise 三種狀態

var p1 = new Promise(function(resolve,reject){
  resolve(1);
});
var p2 = new Promise(function(resolve,reject){
  setTimeout(function(){
    resolve(2);  
  }, 500);      
});
var p3 = new Promise(function(resolve,reject){
  setTimeout(function(){
    reject(3);  
  }, 500);      
});

console.log(p1);
console.log(p2);
console.log(p3);
setTimeout(function(){
  console.log(p2);
}, 1000);
setTimeout(function(){
  console.log(p3);
}, 1000);

p1.then(function(value){
  console.log(value);
});
p2.then(function(value){
  console.log(value);
});
p3.catch(function(err){
  console.log(err);
});
複製程式碼

控制檯:

記錄學習Promise

解析:p1執行的是同步程式碼,所以直接執行,輸出p1是resolved狀態,值為 1。p2,p3中有非同步所以會先處於pending狀態。接著我們通過settimeout函式再延遲1s後輸出,此時p2、p3已經執行完成,狀態分別變成resolved和rejected。

第二個栗子 Promise 狀態的不可逆性

var p1 = new Promise(function(resolve, reject){
  resolve("success1");
  resolve("success2");
});

var p2 = new Promise(function(resolve, reject){
  resolve("success3");
  reject("reject1");
});

p1.then(function(value){
  console.log(value);
});

p2.then(function(value){
  console.log(value);
});

複製程式碼

console中的結果:

記錄學習Promise
解析:Promise狀態一旦變成resolved或rejected時,Promise的狀態和值就固定下來了,不論你後續再怎麼呼叫resolve或reject方法,都不能改變它的狀態和值。因此,p1中resolve("success2")並不能將p1的值更改為success2,p2中reject("reject1")也不能將p2的狀態由resolved改變為rejected.

鏈式呼叫(非常重要,通過此例可以理解then返回物件)

  var p = new Promise(function(resolve, reject){
        resolve(1);
    });
    p.then(function(value){     //第一個then
        console.log(value);     //1
        return value*2;         //返回狀態為resolved的promise物件,值是2
    }).then(function(value){    //第二個then
        console.log(value);     //所以值是2
    }).then(function(value){    //第三個then
        console.log(value);         //因為第二個then中無返回值,所以輸出undefined
        return Promise.resolve('resolve');
    }).then(function(value){     //第四個then
        console.log(value);       //列印出resolve 因為第三個then返回了resolved狀態的Promise物件,Promise物件的值就是這個返回值。
        return Promise.reject('reject');
    }).then(function(value){              //第五個then
        //console.log('resolve: '+ value); //同上 因為返回的是reject 所以列印出reject
     }, function(err){
         console.log('reject: ' + err);
    })

複製程式碼

console的結果:

記錄學習Promise

解析:Promise物件的then方法返回一個新的Promise物件,因此可以通過鏈式呼叫then方法。 then方法接收兩個函式作為引數,第一個引數是Promise執行成功時的回撥,第二個引數是Promise執行失敗時的回撥。兩個函式只會有一個被呼叫,函式的返回值將被用作建立then返回的Promise物件。

這兩個引數的返回值可以是以下三種情況中的一種:

return 一個同步的值 ,或者 undefined(當前一個then沒有返回一個有效值時,預設返回undefined),then方法將返回一個resolved狀態的Promise物件,Promise物件的值就是這個返回值。

return 另一個 Promise,then方法將根據這個Promise的狀態和值建立一個新的Promise物件返回。

throw 一個同步異常,then方法將返回一個rejected狀態的Promise, 值是該異常。

具體分析見程式碼中註釋部分。

Promise 中的異常

var p1 = new Promise( function(resolve,reject){
  foo.bar();  //最先執行,但undefined
  resolve( 1 );	  
});

p1.then(
  function(value){
    console.log('p1 then value: ' + value);
  },
  function(err){
    console.log('p1 then err: ' + err);  //step1:有錯所以執行err,列印出異常資訊
  }
).then(
  function(value){
    console.log('p1 then then value: '+value); //step3
  },
  function(err){
    console.log('p1 then then err: ' + err);
  }
);

var p2 = new Promise(function(resolve,reject){
  resolve( 2 );	
});

p2.then(                 
  function(value){
    console.log('p2 then value: ' + value); //step2:p1的異常得到處理,可以正常執行了
    foo.bar();  
  }, 
  function(err){
    console.log('p2 then err: ' + err);
  }
).then(
  function(value){
    console.log('p2 then then value: ' + value);
  },
  function(err){
    console.log('p2 then then err: ' + err);
    return 1;
  }
).then(
  function(value){
    console.log('p2 then then then value: ' + value);
  },
  function(err){
    console.log('p2 then then then err: ' + err);
  }
);

複製程式碼

console結果:

記錄學習Promise
解析:Promise中的異常由then引數中(而不是下一個then)第二個回撥函式(Promise執行失敗的回撥)處理異常資訊將作為Promise的值。異常一旦得到處理,then返回的後續Promise物件將恢復正常,並會被Promise執行成功的回撥函式處理。另外,需要注意p1、p2 多級then的回撥函式是交替執行的 ,這正是由Promise then回撥的非同步性決定的。

Promise.resolve()

var p1 = Promise.resolve( 1 );
var p2 = Promise.resolve( p1 );
var p3 = new Promise(function(resolve, reject){
  resolve(1);
});
var p4 = new Promise(function(resolve, reject){
  resolve(p1);
});

console.log(p1 === p2); 
console.log(p1 === p3);
console.log(p1 === p4);
console.log(p3 === p4);

p4.then(function(value){
  console.log('p4=' + value);
});

p2.then(function(value){
  console.log('p2=' + value);
})

p1.then(function(value){
  console.log('p1=' + value);
})

複製程式碼

console結果:

記錄學習Promise
解析:Promise.resolve(...)可以接收一個值或者是一個Promise物件作為引數。當引數是普通值時,它返回一個resolved狀態的Promise物件,物件的值就是這個引數;當引數是一個Promise物件時,它直接返回這個Promise引數。因此,p1 === p2。但通過new的方式建立的Promise物件都是一個新的物件,因此後面的三個比較結果都是false。

總結!!

首先,Promise是一個物件,代表了未來某時間才會知道結果的時間,不受外界因素的印象。Promise一旦觸發,其狀態只能變為fulfilled或者rejected,並且已經改變不可逆轉。Promise的建構函式接受一個函式作為引數,該引數函式的兩個引數分別為resolve和reject,其作用分別是將Promise的狀態由pending轉化為fulfilled或者rejected,並且將成功或者失敗的返回值傳遞出去。then有兩個函式作為Promise狀態改變時的回撥函式,當Promise狀態改變時接受傳遞來的引數並呼叫相應的函式。then中的回撥的過程為非同步操作。

catch方法是對.then(null,rejectFn)的封裝(語法糖),用於指定發生錯誤時的回撥函式。一般來說,不要再then中定義rejected狀態的回撥函式,應該使用catch方法代替。all和race都是競速函式,all結束的時間取決於最慢的那個,其作為引數的Promise函式一旦有一個狀態為rejected,則總的Promise的狀態就為rejected;而race結束的時間取決於最快的那個,一旦最快的那個Promise狀態發生改變,那個其總的Promise的狀態就變成相應的狀態,其餘的引數Promise還是會繼續進行的。

另外:關於promise then回撥函式非同步性這部分,我沒有寫出來,但是這部分很重要。可以直接去看js事件執行機制,把這一塊放一起了解會更好。

程式碼例子參考連結:juejin.im/post/597724…