1. 程式人生 > >ES6 Promise物件之例項方法(2)

ES6 Promise物件之例項方法(2)

ES6 Promise物件之例項方法(2)

上一篇關於Promise的文章介紹了Promise的基本用法,這一篇繼續上一篇,介紹Promise的各種方法:

(1)Promise.prototype.then()

then方法是定義在原型物件Promise.prototype上的。它的作用是為 Promise 例項新增狀態改變時的回撥函式。前一篇關於Promise物件的文章中提到過then方法的兩個引數,在此不再敘述。

then方法返回的是一個新的Promise例項(不是原來的Promise例項)。由此可以採用鏈式寫法,即then方法後面再呼叫另一個then方法:

    let
promise = new Promise(function (resolve, reject) { //some code }); promise.then(function (value) { //some code }).then(function (value) { //some code });

使用then方法依次指定了兩個回撥函式,第一個回撥函式完成以後,會將返回結果作為引數傳入第二個回撥函式。如果前一個then回撥函式返回的還是一個Promise物件(即有非同步操作),這時後一個then回撥函式,就會等待該Promise物件的狀態發生變化,才會被呼叫。

(2)Promise.prototype.catch()

1.catch方法用於指定發生錯誤時的回撥函式。
    let promise = new Promise(function (resolve, reject) {
        //some code
    });
    promise.then(function (value) {
        //some code
    }).catch(function (value) {
        //some code
    });

如果Promise例項物件狀態變為Resolved,則呼叫then方法指定的回撥函式;
如果非同步操作丟擲錯誤,Promise例項物件狀態就會變為Rejected,就會呼叫catch方法指定的回撥函式處理這個錯誤。
另外,then方法指定的回撥函式如果在執行中丟擲錯誤,也會被catch方法捕獲。

2.catch方法的作用相當於then方法的第二個回撥函式:
promise.then((value) => console.log(value))
       .catch((error) => console.log(error));

// 等同於
promise.then((value) => console.log(value))
       .then(null, (error) => console.log(error));
3.reject方法的作用等同於丟擲錯誤

Promise丟擲一個錯誤被catch方法指定的函式捕獲,共有三種寫法(三種寫法是等價的):

    let promise = new Promise((resolve, reject) => {
       throw new Error("Error!")
    });
    promise.catch(function (error) {
        console.log(error)
    });
    //Error: Error!
    let promise = new Promise((resolve, reject) => {
        try {
            throw new Error("Error!")
        }
        catch (error) {
            reject(error)
        }

    });
    promise.catch(function (error) {
        console.log(error)
    });
    //Error: Error!
    let promise = new Promise((resolve, reject) => {
        reject(new Error("Error!"))
    });
    promise.catch(function (error) {
        console.log(error)
    });
    //Error: Error!
4.在resolve函式後面再丟擲錯誤,並不會被捕獲
    let promise = new Promise((resolve, reject) => {
        reject("Rejected!");
        throw new Error("Error!")
    });
    promise.catch(function (error) {
        console.log(error)
    });
    //Rejected!
5.Promise物件的錯誤會一直向後傳遞,直到被捕獲為止

也就是說,錯誤總會被下一個catch語句捕獲。

    let promise = new Promise(function (resolve, reject) {
        //some code
    });
    promise.then(function (value) {
        //some code
    }).then(function (value) {
        //some code
    }).catch(function (value) {
        //some code
    });

這裡的catch方法會處理前面Promise例項物件以及兩個then方法執行中丟擲的錯誤。

一般來說,不要在then方法中定義Rejected狀態的回撥函式(then的第二個引數),最好是用catch方法:
因為使用catch方法可以捕獲前面所有then方法中產生的錯誤,而採用then方法的第二個回撥函式無法對同一個then方法中的第一個回撥函式中的錯誤進行處理(對於前一個then方法中的錯誤可以進行處理)。

    promise
        .then(function(data){
           //some code 
        }
        .then(function(data){
           //some code 
        },function(error){
            //some code
        });


    promise
       .then(function(data){
           //some code 
        }
        .then(function(data){
            //some code 
        })
        .catch(function(error){
            //some code
        });

第二種寫法要好於第一種寫法,因為第二種寫法catch方法可以捕獲前面所有then方法執行中的錯誤。

6.如果沒有用catch方法,丟擲的錯誤不會傳遞到外層程式碼
    const someAsyncThing = function() {
        return new Promise(function(resolve, reject) {
            // 下面一行會報錯,因為x沒有宣告
            resolve(x + 2);
        });
    };

    someAsyncThing().then(function() {
        console.log('everything is great');
    });

    setTimeout(() => { console.log(123) }, 2000);

在chrome中會列印如下:

    // Uncaught (in promise) ReferenceError: x is not defined
    // 123

而在火狐中列印如下(沒有列印錯誤提示):

   //123

上面程式碼中,someAsyncThing函式產生的 Promise 物件,內部有語法錯誤。瀏覽器執行到這一行,不會退出程序、終止指令碼執行,2 秒之後還是會輸出123。這就是說,Promise 內部的錯誤不會影響到 Promise 外部的程式碼,通俗的說法就是“Promise 會吃掉錯誤”。
再來看下面的例子:

    const promise = new Promise(function (resolve, reject) {
        resolve('ok');
        setTimeout(function () { throw new Error('test') }, 0)
    });
    promise.then(function (value) { console.log(value) });

    setTimeout(() => { console.log(123) }, 2000);
    // ok
    // Uncaught Error: test
    // 123

上面程式碼中,Promise 指定在下一輪“事件迴圈”再丟擲錯誤。到了那個時候,Promise 的執行已經結束了,所以這個錯誤是在 Promise 函式體外丟擲的,會冒泡到最外層,成了未捕獲的錯誤。

一般總是建議,Promise 物件後面要跟catch方法,這樣可以處理 Promise 內部發生的錯誤。

7.catch方法之後還可以呼叫then方法

catch執行完後返回的還是一個Promise物件,因此後面還可以呼叫then方法。

    Promise.resolve()
        .catch(function (error) {
            console.log("oh no!" + error);
        })
        .then(function (data) {
            console.log("carry on!")
        });
    //carry on!

程式碼執行完catch方法的回撥函式之後會接著執行後面那個then方法。
如果沒有報錯,就會跳過catch方法。
以上程式碼如果then中的回撥函式執行中出現錯誤,就與前面的catch無關了。

8.catch方法還可以再丟擲錯誤
const someAsyncThing = function() {
  return new Promise(function(resolve, reject) {
    // 下面一行會報錯,因為x沒有宣告
    resolve(x + 2);
  });
};

someAsyncThing().then(function() {
  return someOtherAsyncThing();
}).catch(function(error) {
  console.log('oh no', error);
  // 下面一行會報錯,因為 y 沒有宣告
  y + 2;
}).then(function() {
  console.log('carry on');
});
// oh no [ReferenceError: x is not defined]

上面程式碼中,catch方法丟擲一個錯誤,因為後面沒有別的catch方法了,導致這個錯誤不會被捕獲,也不會傳遞到外層。

someAsyncThing().then(function() {
  return someOtherAsyncThing();
}).catch(function(error) {
  console.log('oh no', error);
  // 下面一行會報錯,因為y沒有宣告
  y + 2;
}).catch(function(error) {
  console.log('carry on', error);
});
// oh no [ReferenceError: x is not defined]
// carry on [ReferenceError: y is not defined]

上面程式碼中,第二個catch方法用來捕獲前一個catch方法丟擲的錯誤。

(3)Promise.resolve()

Promise.resolve用於將現有物件轉為Promise物件。下面兩種寫法等價:

Promise.resolve('foo')
// 等價於
new Promise(resolve => resolve('foo'))
1.引數是一個Promise例項

如果引數時Promise例項,那麼Promise.resolve將不做任何修改返回這個例項。

2.引數是一個thenable例項

thenable物件指的是具有then方法的物件,例如:

    let thenable = {
        then: function (resolve,reject) {
            resolve(42);
        }
    }

將這個物件轉為Promise物件:

    let thenable = {
        then: function (resolve,reject) {
            resolve(42);
        }
    };
    let p1=Promise.resolve(thenable);
    p1.then(function(value){
        console.log(value);
    })
    //42
3.引數不具有then方法的物件或根本不是物件

如果引數是一個原始值,或者是一個不具有then方法的物件,Promise.resolve方法會返回一個新的Promise物件,狀態為Resolved。

下面我們先來看一下什麼是原始值:
在ECMAScript中,變數可以存放兩種型別的值:
a、原始值:固定而簡單的值,是存放在棧(stack)中的簡單資料段,它們的值直接儲存在變數訪問的位置。原始型別有以下五種型別:Undefined,Null,Boolean,Number,String;
b、引用值:存放在堆(heap)中的物件,儲存在變數處的是一個指標,指向儲存物件的記憶體地址。所有引用型別都整合自Object。

    //字串
    let p1=Promise.resolve("hello");
    p1.then(function(value){
        console.log(value);
    })
    //hello

    //陣列
    let p2=Promise.resolve([1,2,3]);
    p2.then(function(value){
        console.log(value);
    })
    //[1,2,3]

    //不具有then方法的物件
    let p3=Promise.resolve({"1":1});
    p3.then(function(value){
        console.log(value);
    })
    //{"1":1}

    //null
    let p4=Promise.resolve(null);
    p4.then(function(value){
        console.log(value);
    })
    //null

    //undefined
    let p5 = Promise.resolve(undefined);
    p5.then(function (value) {
        console.log(value);
    })
    //undefined
    
   //數字
    let p6 = Promise.resolve(123);
    p6.then(function (value) {
        console.log(value);
    })
    //123

    //布林值
    let p7 = Promise.resolve(true);
    p7.then(function (value) {
        console.log(value);
    })
    //true

以上Promise例項返回的狀態都是Resolved,所以會呼叫then方法的第一個回撥函式。

4.不帶有任何引數

直接返回一個Resolved狀態的Promise物件。因此,如果希望得到一個Promise物件,比較方便的方法就是直接呼叫Promise.resolve方法。

Promise.resolve()得到一個Promise物件變成Resolved狀態是在本輪“事件迴圈”結束時,而不是在下一輪“事件迴圈”開始時。

    setTimeout(function(){
        console.log("three");
    },0);

    Promise.resolve().then(function(){
        console.log("two");
    });
    console.log("one")
    //one
    //two
    //three

上面程式碼中,setTimeout()是在下一輪“事件迴圈”開始的時候執行的,Promise.resolve()在本輪“事件迴圈”結束的時候執行,console.log(“one”)則立即執行。

(4)Promise.reject()

Promise.reject方法返回一個新的Promise例項,狀態為Rejected:
下面兩種寫法等價:

const p = Promise.reject('出錯了');
// 等同於
const p = new Promise((resolve, reject) => reject('出錯了'))

Promise的例項物件p生成後狀態為Rejected,回撥函式會立即執行:

const p = Promise.reject('出錯了');
p.then(null, function (s) {
  console.log(s)
});
// 出錯了
1.引數是一個Promise例項

將Promise例項的狀態變為Rejected,並返回一個新的Promise物件。reject函式的引數是Promise.reject()方法中傳入的引數(在這裡是Promises例項a):

    let a = Promise.resolve();
    const p = Promise.reject(a);
    p.then(null, function (s) {
        console.log(s);// Promise物件
        console.log(s===a);//true
    });
      console.log(p===a);//false  說明返回新的Promise物件
2.引數是一個thenable例項

將thenable物件轉為Promise物件,新生成的Promise例項的狀態為Rejected。reject函式的引數是Promise.reject()方法中傳入的引數(在這裡是物件thenable1):

    let thenable1 = {
        then: function (resolve,reject) {
            reject(42);
        }
    };
    let p1=Promise.reject(thenable1);
    p1.then(null,function(error){
        console.log(error);//Object
        console.log(error===thenable1);//true
    })

注意這裡與Promise.resolve方法的不同:

    //Promise.resolve方法
    let thenable = {
        then: function (resolve,reject) {
            resolve(42);
        }
    };
    let p1=Promise.resolve(thenable);
    p1.then(function(value){
        console.log(value);
    });
    //42


    //Promise.reject方法
    let thenable1 = {
        then: function (resolve,reject) {
            reject(42);
        }
    };
    let p2=Promise.reject(thenable1);
    p2.then(null,function(error){
        console.log(error);//Object
        console.log(error===thenable1);//true
    })

reject函式的引數是Promise.reject()方法中傳入的引數(這裡是物件thenable1)。

3.引數不具有then方法的物件或根本不是物件

如果引數是一個原始值,或者是一個不具有then方法的物件,Promise.reject方法會返回一個新的Promise物件,狀態為Rejected。

      //字串
    let p1=Promise.reject("hello");
    p1.then(null,function(value){
        console.log(value);
    });
    //hello

    //陣列
    let p2=Promise.reject([1,2,3]);
    p2.then(null,function(value){
        console.log(value);
    })
    //[1,2,3]

    //不具有then方法的物件
    let p3=Promise.reject({"1":1});
    p3.then(null,function(value){
        console.log(value);
    })