Promise對象及相關實例方法介紹

Promise 對象

1.0 Promise 的含義

Promise 是一步編程的一種解決方案, 比傳統的解決方案 — 回調函數和事件 — 更合理和更加強大.

所謂 Promise, 簡單來說就是一個容器, 裏面保存着某個未來纔會結束的事件(通常是一個一步操作)的結果.

Promise 對象有以下兩個特點.

  1. 對象的狀態不受外界影響. Promise 對象代表一個一步操作, 有三種狀態: pending , fulfilledrejected . 只有異步操作的結果, 可以決定當前是哪一種狀態, 其他任何操作都無法改變這個狀態.
  2. 一旦狀態改變, 就不會再變, 任何時候都可以得到這個結果. Promise 對象的狀態改變, 只有兩種可能: 從 pending 變爲 fulfilled 和從 pending變爲rejected. 這要這兩種情況發生, 狀態就凝固了, 不會再改變了, 會一直保持這個結果, 這時就稱爲 resolved(已定性). 如果改變已經發生了, 你再對 promise 對象添加回調函數, 也會立即得到這個結果. 這與事件完全不同, 事件的特點就是, 如果你錯過了它, 你再去監聽,是得不到結果的.

有了 Promise 對象, 就可以將異步的操作 以 同步的流程表達出來., 避免了層層嵌套函數. 此外, Promise 對象提供統一的接口,使得控制異步操作更加容易.

Promise 也有一些缺點,:

  • 首先, 無法取消 Promise, 一旦新建它就會立即執行, 無法取消.
  • 其次, 如果不設置回調函數, Promise 內部拋出的錯誤, 不會反映到外部.
  • 第三, 當處於 pending 狀態時, 無法得知目前進展到哪一個階段(即: 無法得知是剛剛開始還是即將完成).

2.0 基本用法

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

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

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

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

Promise 構造函數接收一個函數作爲參數, 該函數的兩個參數分別是 resolvereject. 它們是兩個函數, 有JavaScript 引擎提供, 不用自己部署.

resolve 函數的作用是, 將 Promise 對象的狀態從"未完成" 變爲 “成功”(即從 pending 變爲 resolved), 在異步操作成功時調用, 並將異步操作的結果, 作爲參數傳遞出去.

reject 函數的作用是, 將 Promise 對象的狀態從 “未完成” 變爲 “失敗” (即從 pending 變爲 rejected), 在異步操作失敗時調用, 並將異步操作爆出的錯誤, 作爲參數傳遞出去.

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

promise.then((value)=>{
    // success
},(error)=>{
    // failure
})

then 方法可以接收兩個回調函數作爲參數,:

  • 第一個回調函數是 Promise 對象的狀態變爲 resolved 時調用
  • 第二個回調函數是 Promise 對象的狀態變爲 rejected 時調用.

其中, 第二個函數是可選的, 不一定要提供. 這兩個函數都接受 Promise 對象傳出的值作爲參數.

下面是一個 Promise 對象的簡單例子.

function timeout(ms) {
  return new Promise((resolve, reject) => {
    setTimeout(resolve, ms, 'done'); // 把 'done' 結果傳給 resolve 函數.
  });
}

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

🏴 注意: setTimeout函數理解

var timeoutID = scope.setTimeout(function[, delay, arg1, arg2, ...]);
var timeoutID = scope.setTimeout(function[, delay]); 
var timeoutID = scope.setTimeout(code[, delay]);

// 參數理解
  function: 是你想要在到期時間之後執行的函數
  delay:  可選, 延遲的時間
  arg1, ... , argN: 可選, 附件參數, 一旦定時器到期, 它們會作爲參數傳遞給 `function` 
  
#  代碼演示如下 ----------------------

setTimeout(function (value1, value2, value3) {
    console.log(value1,value2, value3); // 參數一 參數2 參數三
}, 1000, '參數一', '參數2','參數三')

上面代碼中, 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最後輸出

3.0 Promise.prototype.then()

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

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

getJSON("/posts.json").then(function(json) {
  return json.post;
}).then(function(post) {
  // ...
});

上面的代碼使用then方法,依次指定了兩個回調函數。第一個回調函數完成以後,會將返回結果作爲參數,傳入第二個回調函數。

4.0 Promise.prototype.catch()

Promise.prototype.catch() 方法是 `.then(null,rejection) 或.then(undefined,rejection)的別名, 用於指定發生錯誤時的回調函數.

getJSON('/posts.json').then(function(posts) {
  // ...
}).catch(function(error) {
  // 處理 getJSON 和 前一個回調函數運行時發生的錯誤
  console.log('發生錯誤!', error);
});

上面代碼中,getJSON()方法返回一個 Promise 對象,如果該對象狀態變爲resolved,則會調用then()方法指定的回調函數;如果異步操作拋出錯誤,狀態就會變爲rejected,就會調用catch()方法指定的回調函數,處理這個錯誤。另外,then()方法指定的回調函數,如果運行中拋出錯誤,也會被catch()方法捕獲。

5.0 Promise.prototype.finally()

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

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

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

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

6.0 Promise.all()

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

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

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

p的狀態由p1p2p3決定,分成兩種情況。

(1)只有p1p2p3的狀態都變成fulfilledp的狀態纔會變成fulfilled,此時p1p2p3的返回值組成一個數組,傳遞給p的回調函數。

(2)只要p1p2p3之中有一個被rejectedp的狀態就變成rejected,此時第一個被reject的實例的返回值,會傳遞給p的回調函數。

7.0 Promise.race()

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

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

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

8.0 Promise.resolve()

有時需要將現有的對象轉換爲 Promise 對象, Promise.resolve() 方法就起到了作用

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

上面代碼將jQuery 生成的 deferred 對象, 轉爲一個新的 Promise` 對象.

Promise.resolve() 等價於下面的寫法

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.resolve 方法會將這個對象轉爲 Promise 對象, 然後就立即執行 thenable 對象的 then 方法

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

    上面代碼中,thenable對象的then方法執行後,對象p1的狀態就變爲resolved,從而立即執行最後那個then方法指定的回調函數,輸出 42。

  3. 參數不是具有then方法的對象, 或根本就不是對象

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

const p = Promise.resolve('Hello');

p.then(function (s){
  console.log(s)
});
// Hello

上面代碼生成一個新的 Promise 對象的實例p。由於字符串Hello不屬於異步操作(判斷方法是字符串對象不具有 then 方法),返回 Promise 實例的狀態從一生成就是resolved,所以回調函數會立即執行。Promise.resolve方法的參數,會同時傳給回調函數。

  1. 不帶有任何參數

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

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

const p = Promise.resolve();

p.then(function () {
  // ...
});

上面代碼的變量p就是一個 Promise 對象。

需要注意的是,立即resolve()的 Promise 對象,是在本輪“事件循環”(event loop)的結束時執行,而不是在下一輪“事件循環”的開始時。

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

Promise.resolve().then(function () {
  console.log('two');
});

console.log('one');

// one
// two
// three

上面代碼中,setTimeout(fn, 0)在下一輪“事件循環”開始時執行,Promise.resolve()在本輪“事件循環”結束時執行,console.log('one')則是立即執行,因此最先輸出。

9.0 Promise.rejected()

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

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

p.then(null, function (s) {
  console.log(s)
});
// 出錯了

上面代碼生成一個 Promise 對象的實例p,狀態爲rejected,回調函數會立即執行。

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

const thenable = {
  then(resolve, reject) {
    reject('出錯了');
  }
};

Promise.reject(thenable)
.catch(e => {
  console.log(e === thenable)
})
// true

上面代碼中,Promise.reject方法的參數是一個thenable對象,執行以後,後面catch方法的參數不是reject拋出的“出錯了”這個字符串,而是thenable對象。

總結:

上一章: ES6 Reflect 介紹, Reflect 相關方法介紹

下一章: Generator 函數語法, yield 表達式, next()方法理解

交流學習添加微信(備註技術交流學習): Gene199302
在這裏插入圖片描述

該博客爲學習阮一峯 ES6入門課所做的筆記記錄, 僅用來留作筆記記錄和學習理解

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