ES6躬行記(22)——Promise

  在JavaScript中,回調函數是處理異步編程的經常使用解決方案,但層層嵌套的回調金字塔(以下代碼所示)一直受人詬病,由於不只在視覺上更加混亂,並且在管理上也更爲複雜。編程

setTimeout(() => {
  var reason = "成功執行";
  setTimeout(() => {
    console.log(reason);
  }, 500);
}, 500);

  Promise是ES6新增的特性,能更合理的控制和追蹤異步操做。它是一個包含狀態、可繼承的對象,不只能管理而不是依賴回調,還能以同步的方式傳遞異步的計算結果,從而避免陷入回調金字塔的泥潭中。鏈式(即串聯起來)的Promise讓代碼有更高的可讀性和更便捷的調試性。下面會用Promise實現上一個示例的功能。promise

var promise = new Promise(function(resolve, reject) {
  setTimeout(() => {
    resolve("成功執行");
  }, 500);
});
promise.then(function(value) {
  setTimeout(() => {
    console.log(value);
  }, 500);
});

  示例只是爲了能對Promise有個初步的認識,其中涉及的Promise的建立、then()方法等概念,都將在接下來的章節中作詳細的講解。異步

 

1、狀態

  Promise依據其狀態的變化,讓異步操做變得有序,而Promise有三種互斥的狀態可供選擇,具體以下所列。異步編程

(1)pending:等待中,初始狀態,此時還未處理(Promise中的)異步操做。函數

(2)fulfilled:已完成,異步操做成功時的狀態。spa

(3)rejected:已拒絕,異步操做失敗時的狀態。調試

  每一個Promise只能維護一個狀態,而且狀態只會朝一個方向變化,即從pending變爲fulfilled或rejected,而fulfilled不能變爲rejected,反之也同樣,這種處理狀態的行爲也叫決議。注意,Promise會在內部處理狀態的變化,而且因爲ES6對外沒有暴露訪問Promise狀態的屬性或方法,所以沒法在外部判斷Promise當前處在哪一個狀態。code

2、建立

  若是要使用Promise,那麼須要先初始化,能夠經過構造函數的方式建立一個Promise實例,以下所示。對象

var promise = new Promise(function(resolve, reject) {
  /* executor */
});

  構造函數Promise()能接收一個執行器(executor),即帶有resolve和reject兩個參數的函數,執行器會在構造函數返回新實例前被調用。它的兩個參數也是函數,分別適合不一樣的語境,具體以下所列。blog

(1)在執行器中的異步操做完成時會調用resolve()函數,當前Promise的狀態會根據它的參數發生變化。當參數爲空或非Promise時,當前狀態變成fulfilled;當參數是Promise時,當前Promise的狀態和參數的相同。

(2)在執行器中的異步操做錯誤時會調用reject()函數,當前Promise的狀態會變成rejected。

  resolve()和reject()都能接收一個參數(即決議結果),前者的參數能夠是本次操做的結果;然後者的參數能夠是操做失敗的理由,它們都會傳遞給下一個異步操做。

3、then()

  在生成Promise實例以後,就能經過then()方法綁定狀態變化後的回調函數(即處理方法),以下代碼所示,此處是異步操做同步化的關鍵。

promise.then(function(value) {
  // success
}, function(reason) {
  // failure
});

  then()方法的兩個參數,可分別指定狀態變成fulfilled和rejected後的回調函數,而這兩個回調函數的參數分別來自於resolve()和reject()函數。經過then()方法的這兩個回調函數就能清晰的反饋出異步操做是否成功執行了。

  因爲then()方法的返回值是一個新的Promise實例,所以能夠鏈式調用then()方法,按順序綁定回調函數,以下所示。

var chain = new Promise(function(resolve, reject) {
  reject("error");
});
chain.then(null, function(reason) {
  console.log(reason);        //"error"
  return "end";
})
.then(function(value) {
  console.log(value);         //"end"
});

  雖然第一個then()方法中的已完成的回調函數是null,但並不會終止數據的傳遞,仍然是先輸出「error」,再輸出「end」。之因此是這樣的輸出順序,與回調函數的執行順序有關。在then()方法鏈中,當前Promise的狀態會決定下一個then()方法執行哪一個回調函數,而這個狀態又會受回調函數和它的返回值的影響,具體以下所列。

(1)當返回值是一個非Promise的值時,其狀態會變成fulfilled。

(2)當回調函數拋出一個錯誤時,其狀態會變成rejected。

(3)當返回值是一個Promise時,其狀態與返回值的相同。

  下面有一個示例,描述了第三種狀況,其中Promise.resolve()建立了已完成的Promise,至關於新建立一個在執行器中調用resolve()函數的Promise;Promise.reject()建立了已拒絕的Promise;catch()方法能處理拒絕的回調函數。這些都將在隨後的章節中作詳細介紹。

var chain = new Promise(function(resolve, reject) {
  resolve();
});
chain.then(function(value) {
  return Promise.resolve("fulfilled");
  //至關於
  return new Promise(function(resolve) {
     resolve("fulfilled");
  });
})
.then(function(value) {
  console.log(value);         //"fulfilled"
  return Promise.reject("rejected");
})
.catch(function(reason) {
  console.log(reason);        //"rejected"
});

4、thenable

  包含then()方法的對象被稱爲thenable,全部的Promise都是thenable,下面是一個自定義的thenable,then()方法的參數含義與Promise中的相同。

let tha = {
  then(resolve, reject) {
    reject("thenable");
  }
};

  Promise.resolve()能接收一個thenable,並返回一個新的Promise實例,將上一個示例的tha對象傳遞給它,以下所示。

Promise.resolve(tha)
.catch(function(reason) {
  console.log(reason);        //thenable
}).then(function() {
  console.log("end");
});

  Promise.resolve()能將thenable轉換成已完成或已拒絕的Promise,其最終的狀態取決於thenable的then()方法,像這個示例中的then()方法調用了reject()函數,所以新的Promise的狀態是已拒絕(rejected)。

5、錯誤處理

  Promise的catch()方法能夠捕獲並處理前一個異步操做中拋出的錯誤,它能接收一個已拒絕的回調函數(onRejected),其行爲至關於調用一個忽略已完成的回調函數的then()方法,例如像下面這樣第一個參數傳null或undefined。

catch(onRejected)
//至關於
then(null, onRejected)
then(undefined, onRejected)

  下面是一個使用了catch()方法的例子,先在執行器中拋出一個錯誤,而後在catch()方法中處理。

var error = new Promise(function(resolve, reject) {
  throw "error info";
});
error.catch(function(reason) {
  console.log(reason);        //"error info"
});

  若是Promise的狀態在拋出錯誤以前被改變,那麼這個錯誤就不能被catch()方法捕獲,以下所示。

var error = new Promise(function(resolve, reject) {
  resolve();
  throw "error info";
});
error.catch(function(reason) {
  console.log(reason);        //不會輸出
});

  在執行器中,因爲resolve()函數在throw語句以前被調用,所以「error info」這句錯誤理由就不能在catch()方法中輸出。

  在鏈式的Promise中,一旦發生錯誤,那麼這個錯誤在沒被捕獲前,會一直傳遞下去。爲了確保全部的錯誤都能被處理,可在鏈的末尾加上catch()方法。

相關文章
相關標籤/搜索