1. 程式人生 > >Promise學習小結

Promise學習小結

ace 而且 這一 回調函數 cat prot 其它 修改 嚴格

初步了解Promise

從概念上了解Promise

Promise是一種封裝和組合未來值的易於復用的機制。

Promise 是異步編程的一種解決方案。
所謂Promise,簡單說就是一個容器,裏面保存著某個未來才會結束的事件(通常是一個異步操作)的結果。從語法上說,Promise 是一個對象,從它可以獲取異步操作的消息。Promise 提供統一的API,各種異步操作都可以用同樣的方法進行處理。

Promise對象的兩個特點

對象的狀態不受外界影響。Promise對象代表一個異步操作,有三種狀態:pending(進行中)、fulfilled(已成功)和rejected(已失敗)。只有異步操作的結果,可以決定當前是哪一種狀態,任何其他操作都無法改變這個狀態。這也是Promise這個名字的由來,它的英語意思就是“承諾”,表示其他手段無法改變。

一旦狀態改變,就不會再變,任何時候都可以得到這個結果。Promise對象的狀態改變,只有兩種可能:從pending變為fulfilled和從pending變為rejected。只要這兩種情況發生,狀態就凝固了,不會再變了,會一直保持這個結果,這時就稱為 resolved(已定型)。如果改變已經發生了,你再對Promise對象添加回調函數,也會立即得到這個結果。這與事件(Event)完全不同,事件的特點是,如果你錯過了它,再去監聽,是得不到結果的。

Promise決議後就是外部不可變的值,我們可以安全地把這個值傳遞給第三方,並確信它不會被有意或無意的修改。特別是對於多方查看同一個Promise的情況。

Promise對象的優缺點

優點:

  • 避免了層層嵌套的回調函數
  • Promise對象提供統一的API接口,使控制異步操作更加容易

缺點:

  • 無法取消Promise,一旦新建就會立即執行,無法中途取消
  • 如果不設置回調函數,Promise內部的錯誤不會被反映到外部
  • 當處於Pending狀態時,無法得知進行到什麽地步

Promise的基本用法

ES6 規定,Promise對象是一個構造函數,用來生成Promise實例。

下面代碼創造了一個Promise實例。

const promise = new Promise(function(resolve, reject) {
  // ... some code

  if (/* 異步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});

Promise構造函數接受一個函數作為參數,該函數的兩個參數分別是resolve和reject。它們是兩個函數,由 JavaScript 引擎提供,不用自己部署。

resolve函數的作用是,將Promise對象的狀態從“未完成”變為“成功”(即從 pending 變為 resolved),在異步操作成功時調用,並將異步操作的結果,作為參數傳遞出去;reject函數的作用是,將Promise對象的狀態從“未完成”變為“失敗”(即從 pending 變為 rejected),在異步操作失敗時調用,並將異步操作報出的錯誤,作為參數傳遞出去。

Promise實例生成以後,可以用then方法分別指定resolved狀態和rejected狀態的回調函數

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

then方法可以接受兩個回調函數作為參數。第一個回調函數是Promise對象的狀態變為resolved時調用,第二個回調函數是Promise對象的狀態變為rejected時調用。其中,第二個函數是可選的,不一定要提供。這兩個函數都接受Promise對象傳出的值作為參數。

Promise用法示例

function timeout(ms) {
  return new Promise((resolve, reject) => {
    setTimeout(resolve, ms, ‘done‘);
  });
}

timeout(100).then((value) => {
  console.log(value);
});

上面代碼中,timeout方法返回一個Promise實例,表示一段時間以後才會發生的結果。過了指定的時間(ms參數)以後,Promise實例的狀態變為resolved,就會觸發then方法綁定的回調函數。

Promise 新建後就會立即執行。

let promise = new Promise(function(resolve, reject) {
  console.log(‘Promise‘);
  resolve();
});

promise.then(function() {
  console.log(‘resolved.‘);
});

console.log(‘Hi!‘);

// Promise
// Hi!
// resolved

上面代碼中,Promise新建後立即執行,所以首先輸出的是Promise。然後,then方法指定的回調函數,將在當前腳本所有同步任務執行完才會執行,所以resolved最後輸出。

一般來說,調用resolve或reject以後,Promise的使命就完成了,後繼操作應該放到then方法裏面,而不應該直接寫在resolve或reject的後面。所以,最好在它們前面加上return語句,這樣就不會有意外。

new Promise((resolve, reject) => {
  return resolve(1);
  // 後面的語句不會執行
  console.log(2);
})

Promise模式

Promise鏈的順序模式—this-then-this-then-that。

可以基於Promise構建的異步模式抽象還有很多變體,這些模式是為了簡化異步流程控制。原生ES6 promise模塊中直接支持實現了兩個這樣的模式,我們可以直接使用它們,用作構建其它模式的基本塊。

Promise.all([ .. ])

在異步序列中,任意時刻都只能有一個異步任務正在執行,一個任務只能在一個任務之後。想要同時執行兩個或者更多的任務時,也就是並行執行時,可使用Promise中的 all([ .. ]) 模式。

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

示例:

const p = promise.all([p1,p2]);

Promise需要一個參數,是一個數組,通常由Promise實例組成。從Promise.all([ .. ])調用返回的promise會收到一個完成消息。這是一個由所有傳入promise的完成消息組成的數組,與指定的順序一致,與完成順序無關。

嚴格來說,傳給Promise.all([ .. ]) 的數組中的值可以是Promise,thenable,甚至是立即值。就本質而言,列表中的每個值都會通過Promise.resolve(..)過濾,以確保要等待的是一個真正的Promise,所以立即值會被規範化為為這個值構建的Promise。如果數組是空的,主Promise就會立即完成。

從Promise.all([ .. ])返回的主promise在且僅在所有成員promise都完成後才會完成。如果這些promise中有一個被拒絕的話,主Promise.all([ .. ]) promise就會立即被拒絕,並丟棄來自其他promise的全部結果。

註意:永遠要記住為每一個promise關聯一個拒絕/錯誤處理函數,特別是從Promise.all([ .. ])返回的那一個。

Promise.race([ .. ])

盡管Promise.all([ .. ]) 協調多個並發Promise的運行,並假定所有Promise都需要完成,但有時候可能只需要第一個Promise,而需要拋棄其他Promise。這種模式在Promise中稱為競態。

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

示例:

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

Promise.race([ .. ])方法的參數與Promise.all([ .. ])一樣。

與Promise.all([ .. ])類似,在Promise.race([ .. ])方法中,一旦有任何一個Promise決議為完成,Promise.race([ .. ])就會完成;一旦有任何一個Promise決議為拒絕,Promise.race([ .. ])方法就會拒絕。

競態條件下,一場競賽至少需要一個競賽者。所以,如果你傳入了一個空數組,Promise永遠不會決議,而不是立即決議。而因為Promise庫建立時間早於ES6Promise,所以這個問題在ES6中仍存在,所以要註意,不要向Promise.race([ .. ])中傳遞空數組。

all([ .. ])和race([ .. ])的變體

原生ES6 Promise提供了內建的Promise.all([ .. ])和Promise.race([ .. ]),但這些語義還有其他幾個常用的變體模式。

none([ .. ])

這個模式類似於all([ .. ]),不過完成和拒絕的情況互換了。所有的Promise都要被拒絕,即拒絕轉化為完成值,反之亦然。

any([ .. ])

這個模式類似於all([ .. ]),但是會忽略拒絕,即需要完成一個而不是全部。

first([ .. ])

這個模式類似於all([ .. ])的競爭,即只要第一個Promise完成,它就會忽略後續的任何拒絕和完成。

如果所有的promise都拒絕,主promise不會拒絕,它只會掛住。類似於Promise([ .. ])。如果需要的話,可以添加額外的邏輯跟蹤每個promise拒絕,如果所有的promise都被拒絕,就在主promise上調用reject()。

last([ .. ])

這個模式類似於first([ .. ]),但卻是只有最後一個完成勝出。

有些庫提供了這些支持,但這些變體也可以通過Promise,all([ .. ]),race([ .. ])來實現。

Promise API概述

new Promise(...)構造器

有啟示性的構造器Promise(..)必須和new一起使用,並且必須提供一個函數回調。這個回調是同步的或立即調用的。這個函數接受兩個函數回調,用以支持Promise的決議。通常我們把這兩個函數成為resolve(..)和reject()。

Promise.resolve(..)和Promise.reject(..)

Promise.resolve(..):

Promise.resolve(..)常用於創建一個已完成的Promise。

如果傳入的是真正的Promise,Promise.resolve(..)什麽都不會做,只會直接把這個值返回。

如果傳入的參數是一個thenable對象時(thenable對象指的是具有then方法的對象),Promise.resolve方法會將這個對象轉為Promise對象,然後就立即執行thenable對象的then方法。

如果參數是一個原始值,或者是一個不具有then方法的對象,則Promise.resolve方法返回一個新的Promise對象,狀態為resolved。

Promise.resolve方法允許調用時不帶參數,直接返回一個resolved狀態的 Promise 對象。

所以,如果希望得到一個Promise對象,比較方便的方法就是直接調用Promise.resolve方法。

Promise.reject(..)

Promise.reject(reason)方法也會返回一個新的 Promise 實例,該實例的狀態為rejected。

Promise.reject()方法的參數,會原封不動地作為reject的理由,變成後續方法的參數。這一點與Promise.resolve方法不一致。

Promise.prototype.then(..)和Promise.prototype.catch(..)

每個Promise實例都有then(..)和catch(..)方法,通過這兩個方法可以為這個Promise註冊完成和拒絕處理函數。Promise決議後,立即會調用這兩個處理函數之一,但不會兩個都調用,而且總是異步調用。

then(..):

Promise 實例具有then方法,也就是說,then方法是定義在原型對象Promise.prototype上的。它的作用是為 Promise 實例添加狀態改變時的回調函數。前面說過,then方法的第一個參數是resolved狀態的回調函數,第二個參數(可選)是rejected狀態的回調函數。

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

catch(..):

對於catch(..)只接受一個拒絕回調作為參數,並自動替換默認完成回調。Promise.prototype.catch方法是.then(null, rejection)的別名,用於指定發生錯誤時的回調函數。

建議總是使用catch方法,而不使用then方法的第二個參數。

Promise.prototype.finally()

finally方法用於指定不管 Promise 對象最後狀態如何,都會執行的操作。該方法是 ES2018 引入標準的。

promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});

上面代碼中,不管promise最後的狀態,在執行完then或catch指定的回調函數以後,都會執行finally方法指定的回調函數。

下面是一個例子,服務器使用 Promise 處理請求,然後使用finally方法關掉服務器。

server.listen(port)
  .then(function () {
    // ...
  })
  .finally(server.stop);

finally方法的回調函數不接受任何參數,這意味著沒有辦法知道,前面的Promise狀態到底是fulfilled還是rejected。這表明,finally方法裏面的操作,應該是與狀態無關的,不依賴於 Promise 的執行結果。

finally本質上是then方法的特例。

Promise局限性

順序錯誤處理

單一值

單決議

慣性

無法取消的Promise

Promise性能

Promise學習小結