ES6-新內容(三)Promise對象

一、Promise對象

1.特點

Promise對象代表一個異步操作,有三種狀態:Pending(進行中)、Resolved(已完成,又稱Fulfilled)和Rejected(已失敗)。
一旦狀態改變,就不會再變,任何時候都可以得到這個結果。
有了Promise對象,就可以將異步操作以同步操作的流程表達出來,避免了層層嵌套的回調函數。

2.基本用法

var promise=new Promise(function(resolve,reject){
	//...
	if(/*異步操作成功*/){
		resolve(value);
	}else{
		reject(value);
	}
});
//調用
promise.then(function(value) {
  // success
}, function(error) {
  // failure
});

resolve:將Promise對象的狀態從“未完成”變爲“成功”(即從Pending變爲Resolved),在異步操作成功時調用,並將異步操作的結果,作爲參數傳遞出去。
reject:將Promise對象的狀態從“未完成”變爲“失敗”(即從Pending變爲Rejected),在異步操作失敗時調用,並將異步操作報出的錯誤,作爲參數傳遞出去。

3.API

1)then()

它的作用是爲Promise實例添加狀態改變時的回調函數。

then方法返回的是一個新的Promise實例(注意,不是原來那個Promise實例)。因此可以採用鏈式寫法,即then方法後面再調用另一個then方法。
示例:

getJSON("/post/1.json").then(
  post => getJSON(post.commentURL)
).then(
  comments => console.log("Resolved: ", comments),
  err => console.log("Rejected: ", err)
);

2)catch()

catch方法是then(null, rejection)的別名,用於指定發生錯誤時的回調函數。

下面代碼中:

  • getJSON方法返回一個Promise對象,如果該對象狀態變爲Resolved,則會調用then方法指定的回調函數;
  • 如果異步操作拋出錯誤,狀態就會變爲Rejected,就會調用catch方法指定的回調函數,處理這個錯誤。
  • 另外,then方法指定的回調函數,如果運行中拋出錯誤,也會被catch方法捕獲。
getJSON("/posts.json").then((posts)=>{
  // ...
}).catch(// 處理 getJSON 和 前一個回調函數運行時發生的錯誤
  error=>console.log('發生錯誤!', error);
);

如果Promise狀態已經變成Resolved,再拋出錯誤是無效的。

Promise對象的錯誤具有“冒泡”性質,會一直向後傳遞,直到被捕獲爲止。也就是說,錯誤總是會被下一個catch語句捕獲。

getJSON("/post/1.json").then(function(post) {
  return getJSON(post.commentURL);
}).then(function(comments) {
  // some code
}).catch(function(error) {
  // 處理前面三個Promise產生的錯誤
});

一般來說,不要在then方法裏面定義Reject狀態的回調函數(即then的第二個參數),總是使用catch方法。

跟傳統的try/catch代碼塊不同的是,如果沒有使用catch方法指定錯誤處理的回調函數,Promise對象拋出的錯誤不會傳遞到外層代碼,即不會有任何反應。

catch方法返回的還是一個Promise對象,因此後面還可以接着調用then方法。

下面代碼中,第二個catch方法用來捕獲,前一個catch方法拋出的錯誤。

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

3)Promise.all()

Promise.all方法用於將多個Promise實例,包裝成一個新的Promise實例。

Promise.all方法接受一個數組作爲參數,p1、p2、p3都是Promise對象的實例,如果不是,就會先調用下面講到的Promise.resolve方法,將參數轉爲Promise實例,再進一步處理。(Promise.all方法的參數可以不是數組,但必須具有Iterator接口,且返回的每個成員都是Promise實例。)

var p = Promise.all([p1, p2, p3]);

p的狀態由p1、p2、p3決定,分成兩種情況:

  • 只有p1、p2、p3的狀態都變成fulfilled,p的狀態纔會變成fulfilled,此時p1、p2、p3的返回值組成一個數組,傳遞給p的回調函數。
  • 只要p1、p2、p3之中有一個被rejected,p的狀態就變成rejected,此時第一個被reject的實例的返回值,會傳遞給p的回調函數。

4)Promise.race()

同樣是將多個Promise實例,包裝成一個新的Promise實例。

var p = Promise.race([p1, p2, p3]);

上面代碼中,只要p1、p2、p3之中有一個實例率先改變狀態,p的狀態就跟着改變。那個率先改變的 Promise 實例的返回值,就傳遞給p的回調函數。

5)Promise.resolve()

有時需要將現有對象轉爲Promise對象,Promise.resolve方法就起到這個作用。

var jsPromise = Promise.resolve($.ajax('/whatever.json'));

Promise.resolve方法的參數分成四種情況:

  • 參數是一個Promise實例:那麼Promise.resolve將不做任何修改、原封不動地返回這個實例。
  • 參數是一個thenable對象:thenable對象指的是具有then方法的對象。會將這個對象轉爲Promise對象,然後就立即執行thenable對象的then方法。
  • 參數不是具有then方法的對象,或根本就不是對象:則Promise.resolve方法返回一個新的Promise對象,狀態爲Resolved。
  • 不帶有任何參數:直接返回一個Resolved狀態的Promise對象。如果希望得到一個Promise對象,比較方便的方法就是直接調用Promise.resolve方法。

6)Promise.reject()

返回一個新的Promise實例,該實例的狀態爲rejected。它的參數用法與Promise.resolve方法完全一致。

7)Promise.try()

讓同步函數同步執行,異步函數異步執行,並且讓它們具有統一的 API 。

const f = () => console.log('now');
Promise.try(f);
console.log('next');
// now
// next

由於Promise.try爲所有操作提供了統一的處理機制,所以如果想用then方法管理流程,最好都用Promise.try包裝一下。可以更好地管理異常。

事實上,Promise.try就是模擬try代碼塊,就像promise.catch模擬的是catch代碼塊。

8)附加方法:done()

Promise對象的回調鏈,不管以then方法或catch方法結尾,要是最後一個方法拋出錯誤,都有可能無法捕捉到(因爲Promise內部的錯誤不會冒泡到全局)。因此,我們可以提供一個done方法,總是處於回調鏈的尾端,保證拋出任何可能出現的錯誤。

asyncFunc()
  .then(f1)
  .catch(r1)
  .then(f2)
  .done();

實現代碼:

Promise.prototype.done = function (onFulfilled, onRejected) {
  this.then(onFulfilled, onRejected)
    .catch(function (reason) {
      // 拋出一個全局錯誤
      setTimeout(() => { throw reason }, 0);
    });
};

9)附加方法:finally()

用於指定不管Promise對象最後狀態如何,都會執行的操作。與done方法的最大區別,它接受一個普通的回調函數作爲參數,該函數不管怎樣都必須執行。
示例:

server.listen(0)
  .then(function () {
    // run test
  })
  .finally(server.stop);

實現代碼:

Promise.prototype.finally = function (callback) {
  let P = this.constructor;
  return this.then(
    value  => P.resolve(callback()).then(() => value),
    reason => P.resolve(callback()).then(() => { throw reason })
  );
};

4.應用

1)加載圖片

將圖片的加載寫成一個Promise,一旦加載完成,Promise的狀態就發生變化。

const preloadImage = function (path) {
  return new Promise(function (resolve, reject) {
    var image = new Image();
    image.onload  = resolve;
    image.onerror = reject;
    image.src = path;
  });
};

2)Generator函數與Promise的結合

使用Generator函數管理流程,遇到異步操作的時候,通常返回一個Promise對象。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章