手撕遵循 Promise/A+ 規範的 Promise

相比於回調函數,Promise 解決了 「回調地獄」 和 「信任問題」 等痛點,而且大大提升了代碼的可讀性。在現代前端開發中,Promise 幾乎成了處理異步的首選(雖然還有更方便的 async/await,逃)。這篇文章從 Promise 的思想和運行機制入手,深刻理解每一個 API,最後手寫一個遵循 Promise/A+ 規範的 Promise 來。前端

異步方式

JavaScript 異步方式共有有下面六種。git

  • 事件監聽github

  • 回調函數面試

  • 發佈/訂閱ajax

  • Promise算法

  • 生成器json

  • async/await小程序

回調函數

面試中被問到 回調函數 有什麼缺點,相信你必定不假思索地回答 回調地獄。的確如此,當咱們須要發送多個異步請求,而且每一個請求之間須要相互依賴時,就會產生回調地獄。微信小程序

前段時間寫了一個天氣微信小程序 Natsuha,它獲取天氣的邏輯大體以下(固然真實場景複雜的多)。api

  • 首先要獲取用戶的經緯度 (接口 A)

  • 根據經緯度反查城市 (接口 B)

  • 根據城市拿到相應的天氣信息 (接口 C)

按照回調的方式去處理這個邏輯,大體會寫成下面的樣子:

ajax(A, () => {
  // 獲取經緯度
  ajax(B, () => {
    // 根據經緯度反查城市
    ajax(C, () => {
      // 根據城市獲取天氣信息
    });
  });
});
複製代碼

看起來很醜陋不是嗎?相信你們對回調函數的缺點大體都瞭解,這裏就不展開,只作個總結。

  • 代碼邏輯書寫順序與執行順序不一致,不利於閱讀與維護。

  • 異步操做的順序變動時,須要大規模的代碼重構。

  • 回調函數基本都是匿名函數,bug 追蹤困難。

  • 回調函數是被第三方庫代碼(如上例中的 ajax )而非本身的業務代碼所調用的,形成了控制反轉(IoC)。

簡單談一談 控制反轉,《你不知道的 JavaScript (中卷)》把回調函數的最大缺點歸結爲 信任問題。例子中 ajax 是一個三方的函數(你徹底能夠把它想象成 jQuery 的 $.ajax()),咱們把本身的業務邏輯,也就是將回調函數 交給了 ajax 去處理。但 ajax 對咱們來講僅僅是一個黑盒,若是 ajax 自己有缺陷的話,咱們的回調函數就處於危險之中,這也就是所謂的「信任問題」。

不過 Promise 的出現解決了這些缺點,它可以把控制反轉再反轉回來。這樣的話,咱們能夠不把本身程序的傳給第三方,而是讓第三方給咱們提供瞭解其任務什麼時候結束的能力,進而由咱們本身的代碼來決定下一步作什麼。

何爲 Promise

《你不知道的 JavaScript (中卷)》舉了一個例子:

我在快餐店點了一個漢堡,並支付了 1.07 美金。這意味着我對某個值(漢堡)發出了請求。

接着收銀員給我一張 取餐單據,它保證了我最終會獲得漢堡,所以 取餐單據 就是一個 承諾

在等待取餐的過程當中,我能夠作點其餘的事情,好比刷刷推特,看看 996.icu 今天又漲了多少 star。之因此我可作點兒其餘的事情,是由於 取餐單據 表明了我 將來的 漢堡。它在某種意義上已經成了漢堡的 佔位符。從本質上來說,這個 佔位符 使得這個值再也不依賴時間,這是一個 將來值

終於,我聽到服務員在喊 250號前來取餐,我就能夠拿着 取餐單據 換個人漢堡了。

可是可能還有另外一種結果,在我去取餐時,服務員充滿抱歉的告訴我漢堡已經售罄了,除了憤怒,咱們還能夠看到 將來值 可能成功,也可能失敗。

Promise 基礎知識

Promise 的生命週期

每一個 Promise 都會經歷一個短暫的生命週期:先是處於 進行中 (pending),此時操做還沒有完成,所以它也是 未處理 (unsettled) 的;一旦異步操做執行結束,Promise 變成 已處理 (settled) 狀態,此時它會進入到如下兩個狀態中的其中一個:

  • Fulfilled:Promise 異步操做成功完成

  • Rejected:因爲程序錯誤或其餘緣由,異步操做未能成功完成

Promise 構造函數

Promise 自己是一個構造函數,它接收一個叫作 executor 的函數,該函數會被傳遞兩個名爲 resolve()reject() 的函數做爲參數。resolve() 函數在執行器成功時被調用,而 reject() 在執行器操做失敗後被調用。看下面這個例子。

const fs = require('fs');

const promise = path =>
  // 執行器接收 resolve() 和 reject() 做爲參數
  new Promise((resolve, reject) => {
    fs.readFile(__dirname + '/' + path, 'utf-8', (err, data) => {
      if (err) {
        // 失敗時調用 reject()
        reject(err);
        return;
      }
      // 成功時時調用 resolve()
      resolve(data);
    });
  });
複製代碼

Promise 的 then 方法

then() 方法接收兩個函數做爲參數,第一個做爲 完成 時的回調,第二個做爲 拒絕 時的回調。兩個參數均爲可選,所以你能夠只監聽 完成,或者只監聽 拒絕。其中當第一個參數爲 null,第二個參數爲回調函數時,它意味着監聽 拒絕。在實際應用中,完成拒絕 都應當被監聽。

const promise = new Promise((resolve, reject) => {
  resolve('success');
});

// 監聽完成和拒絕
promise.then(
  res => {
    // 完成
    console.log(res);
  },
  e => {
    // 拒絕
    console.log(e);
  },
);

// 只監聽完成
promise.then(res => {
  console.log(res);
});

// 第一個參數爲 null 時意味着拒絕
promise.then(null, res => {
  // 完成
  console.log(res);
});
複製代碼

Promise 還有兩個方法分別是 catch()finally(),前者用於監聽 拒絕,後者不管成功失敗都會被執行到。鏈式調用顯然可讀性更高,因此咱們推薦下面這種寫法。

promise
  .then(res => {
    console.log(res);
  })
  .catch(e => {
    console.log(e);
  })
  .finally(() => {
    console.log('不管成功失敗都會執行這句');
  });
複製代碼

Promise 鏈式調用

每次調用 then() 或 catch() 方法時都會 建立並返回一個新的 Promise,只有當前一個 Promise 完成或被拒絕後,下一個纔會被解決。

看下面這個例子,p.then() 完成後返回第二個 Promise,接着又調用了它的 then() 方法,也就是說只有當第一個 Promise 被解決以後纔會調用第二個 then() 方法的 then()

let p = new Promise((resolve, reject) => {
  resolve(42);
});

p.then(value => {
  console.log(value); // 42
}).then(() => {
  console.log('能夠執行到'); // '能夠執行到'
});
複製代碼

將上述示例拆開,看起來是這樣的。調用 p1.then() 的結果被存儲到 p2 中,p2.then() 被調用來添加最終的 then()

let p1 = new Promise((resolve, reject) => {
  resolve(42);
});

let p2 = p1.then(value => {
  console.log(value);
});

p2.then(() => {
  console.log('能夠執行到');
});
複製代碼

咱們經過一個實例來看一下鏈式調用。下面是獲取城市天氣的場景:咱們首先須要調用 getCity 接口來獲取 城市id,接着調用 getWeatherById/城市id 來獲取城市的天氣信息。首先用 Promise 封裝一個原生 Ajax。(敲黑板,面試可能要求手寫)

const getJSON = function(url) {
  const promise = new Promise(function(resolve, reject) {
    const handler = function() {
      if (this.readyState !== 4) {
        return;
      }
      if (this.status === 200) {
        resolve(this.response);
      } else {
        reject(new Error(this.statusText));
      }
    };
    const client = new XMLHttpRequest();
    client.open('GET', url);
    client.onreadystatechange = handler;
    client.responseType = 'json';
    client.setRequestHeader('Accept', 'application/json');
    client.send();
  });

  return promise;
};

const baseUrl = 'https://5cb322936ce9ce00145bf070.mockapi.io/api/v1';
複製代碼

經過鏈式調用來請求數據,最後別忘了捕獲錯誤。

getJSON(`${baseUrl}/getCity`)
  .then(value => getJSON(`${baseUrl}/getWeatherById/${value.cityId}`))
  .then(value => console.log(value))
  .catch(e => {
    console.log(e);
  });
複製代碼

捕獲錯誤

當 then() 方法或者 catch() 方法拋出錯誤時,鏈式調用的下一個 Promise 中的 catch() 方法能夠經過 catch() 接收這個錯誤。側面來說,異常不必定只發生在 Promise 中,還有可能發生在 then() 或者 catch() 中。

let p1 = new Promise((resolve, reject) => {
  resolve(42);
});

p1.then(value => {
  throw new Error(' `then()` 錯誤');
}).catch(e => {
  console.log(e.message); // ' `then()` 錯誤'
});
複製代碼

不只 then() 能夠拋出異常,catch() 也能夠拋出的異常,且能夠被下一個 catch() 捕獲。所以,不管如何都應該在 Promise 鏈的末尾留一個 catch() ,以保證可以正確處理全部可能發生的錯誤。看下面這個例子。

let p1 = new Promise((resolve, reject) => {
  throw new Error('執行器錯誤');
});

p1.catch(e => {
  console.log(e.message); // '執行器錯誤'
  throw new Error(' `catch()` 錯誤');
}).catch(e => {
  console.log(e.message); // ' `catch()` 錯誤'
});
複製代碼

Promise 鏈的返回值

Promise 鏈的一個重要特性是能從一個 Promise 傳遞數據給下一個 Promise,經過完成處理函數的返回值,來將數據沿着一個鏈傳遞下去。咱們看下面這個例子。

function task() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('task');
    }, 1000);
  });
}

task()
  .then(res => {
    console.log(res);
    return 'taskB';
  })
  .then(res => {
    console.log(res);
    return 'taskC';
  })
  .then(res => {
    console.log(res);
    throw new Error();
  })
  .catch(e => {
    console.log(e);
    return 'taskD';
  })
  .then(res => {
    console.log(res);
  });
複製代碼

Jietu20190415-172853.jpg

運行結果如上圖所示。咱們知道,每次調用 then() 或者 catch() 都會返回一個新的 Promise 實例,經過指定處理函數的返回值,能夠沿着一個鏈繼續傳遞數據。

所以第一個 then() 將 'taskB' 做爲下一個 then() 的參數傳遞下去,一樣第二個 then() 將 'taskC' 做爲第三個 then() 的參數傳遞下去。

而第三個 then() 裏面拋出一個異常,上面說處處理函數中的拋出異常必定會被後面的拒絕處理函數捕獲,因此 catch() 裏可以打印出上一個 then() 的錯誤。

別忘了 catch() 返回 'taskD' 也能夠被最後一個 then() 捕獲。

其餘構造方法

Promise.resolve() 和 Promise.reject()

Promise.resolve() 和 Promise.reject() 相似於快捷方式,用來建立一個 已完成已被拒絕 的 promise。此外,Promise.resolve() 還能接受非 Promise 的 thenable 的做爲參數,也就是所謂 擁有 then 方法的對象

// p1 和 p2 等價
const p1 = new Promise((resolve, reject) => {
  reject('Oops');
});

const p2 = Promise.reject('Oops');

// p3 和 p4 等價
const p3 = new Promise((resolve, reject) => {
  resolve('Oops');
});

const p4 = Promise.resolve('Oops');
複製代碼

而對於 Promise.resolve(),它還能接收一個非 Promise 的 thenable 做爲參數。它能夠建立一個已完成的 Promise,也能夠建立一個以拒絕的 Promise。

let thenable1 = {
  then(resolve, reject) {
    resolve(1);
  },
};

let p1 = Promise.resolve(thenable1);

p1.then(value => console.log(value)); // 1

let thenable2 = {
  then(resolve, reject) {
    reject(1);
  },
};

let p2 = Promise.resolve(thenable2);

p2.catch(reason => console.log(reason)); // 1
複製代碼

Promise.all()

該方法接收單個迭代對象(最多見的就是數組)做爲參數,並返回一個 Promise。這個可迭代對象的元素都是 Promise,只有在它們都完成後,所返回的 Promise 纔會被完成。

  • 當全部的 Promise 均爲完成態,將會返回一個包含全部結果的數組。

  • 只要有一個被拒絕,就不會返回數組,只會返回最早被拒絕的那個 Promise 的緣由

let p1 = new Promise((resolve, reject) => {
  resolve(42);
});
let p2 = new Promise((resolve, reject) => {
  reject(43);
});
let p3 = new Promise((resolve, reject) => {
  reject(44);
});

let p4 = new Promise((resolve, reject) => {
  resolve(45);
});

// 所有完成,返回數組
let p5 = Promise.all([p1, p4]);
p5.then(value => console.log(value)); // [42, 45]

// 只要有一個出錯,就不會返回數組,且只會返回最早被拒絕的那個 Promise 的緣由
let p6 = Promise.all([p1, p2, p3, p4]);
p6.catch(value => console.log(value)); // 43
複製代碼

Promise.race()

該方法一樣接收單個迭代對象(最多見的就是數組)做爲參數,不一樣的是,該方法只要檢測到任意一個被解決,該方法就會作出響應。所以一個有趣的例子是把 請求接口 和一個 setTimeout 進行競逐,若是 setTimeout 先作出響應,就證實這個接口請求超時。

const p = Promise.race([
  fetch('/some-api'),
  new Promise((resolve, reject) => {
    setTimeout(() => reject(new Error('請求超時')), 3000);
  }),
]);

p.then(value => {
  console.log(value);
}).catch(reason => {
  console.log(reason);
});
複製代碼

Promise 的侷限性

看起來 Promise 很美好,解決了回調函數的種種問題,但它也有本身的侷限性。

  • 一旦建立一個 Promise 併爲其註冊完成/拒絕處理函數,Promise 將沒法被取消。

  • 當處於 pending 狀態時,你沒法得知當前進展到哪一塊

  • 由於 Promise 只能被決議一次(完成或拒絕),若是某些事件不斷髮生,stream 模式會更合適。

  • 若是不設置回調函數,Promise 內部拋出的錯誤,不會反應到外部。

手撕代碼

手撕代碼的以前能夠參照一下後面的 Promise A+ 規範翻譯,最好仍是本身去官網翻譯一遍,這樣寫起來纔會駕輕就熟。下面的代碼幾乎每句都加了註釋,而且連接到每一條規範。

const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

class Promise {
  constructor(executor) {
    // state 的初始狀態爲等待態
    this.state = PENDING;

    // 成功的值 (1.3)
    this.value = undefined;

    // 失敗的緣由 (1.5)
    this.reason = undefined;

    // 由於 then 在相同的 promise 能夠被調用屢次,因此須要將全部的 onFulfilled 存到數組 (2.2.6)
    this.onResolvedCallbacks = [];

    // 由於 then 在相同的 promise 能夠被調用屢次,因此須要將全部的 onRejected 存到數組 (2.2.6)
    this.onRejectedCallbacks = [];

    const resolve = value => {
      // 只有當前是 pending,纔可能轉換爲 fulfilled
      // 而且不能再轉換成其餘任何狀態,且必須擁有一個不可變的值
      if (this.state === PENDING) {
        this.state = FULFILLED;
        this.value = value;
        // onFulfilled 回調按原始調用順序依次執行 (2.2.6.1)
        this.onResolvedCallbacks.forEach(fn => fn());
      }
    };

    const reject = reason => {
      // 只有當前是 pending,纔可能轉換爲 rejected
      // 而且不能再轉換成其餘任何狀態,且必須擁有一個不可變的緣由
      if (this.state === PENDING) {
        this.state = REJECTED;
        this.reason = reason;
        // onRejectec 回調按原始調用順序依次執行 (2.2.6.1)
        this.onRejectedCallbacks.forEach(fn => fn()); // (2.2.6.2)
      }
    };

    // 若 executor 報錯,直接執行 reject()
    try {
      executor(resolve, reject);
    } catch (err) {
      reject(err);
    }
  }

  then(onFulfilled, onRejected) {
    // onFulfilled 和 onRejected 都是可選參數 (2.2.1)

    // 若是 onFulfilled 不是函數,則必須將它忽略 (2.2.1.1)
    onFulfilled =
      typeof onFulfilled === 'function' ? onFulfilled : value => value;

    // 若是 onRejected 不是函數,則必須將它忽略 (2.2.1.2)
    onRejected =
      typeof onRejected === 'function'
        ? onRejected
        : err => {
            throw err;
          };

    // 爲了作到鏈式調用,規定每一個 then 方法必須返回一個 promise,稱爲 promise2
    const promise2 = new Promise((resolve, reject) => {
      // 在 promise 完成後方可調用 onFulfilled (2.2.2)
      if (this.state === FULFILLED) {
        // onFulfilled/onRejected 必須被異步調用,所以咱們用延時函數模擬 (2.2.4)
        setTimeout(() => {
          try {
            // value 做爲完成函數的第一個參數 (2.2.2.1)
            // onFulfilled 函數被記作 x (2.2.7.1)
            const x = onFulfilled(this.value);
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            // 若是 onFulfilled/onRejected 拋出異常,則 promise2 必須拒絕執行,並返回拒因 e (2.2.7.2)
            reject(e);
          }
        }, 0);
      }

      // 在 promise 被拒絕後方可調用 onRejected (2.2.3)
      if (this.state === REJECTED) {
        // onFulfilled/onRejected 必須被異步調用,所以咱們用延時函數模擬 (2.2.4)
        setTimeout(() => {
          try {
            // reason 做爲拒絕函數的第一個參數 (2.2.3.1)
            // onRejected 函數被記作 x (2.2.7.1)
            const x = onRejected(this.reason);
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            // 若是 onFulfilled/onRejected 拋出異常,則 promise2 必須拒絕執行,並返回拒因 e (2.2.7.2)
            reject(e);
          }
        }, 0);
      }

      if (this.state === PENDING) {
        this.onResolvedCallbacks.push(() => {
          // onFulfilled/onRejected 必須被異步調用,所以咱們用延時函數模擬 (2.2.4)
          setTimeout(() => {
            try {
              const x = onFulfilled(this.value);
              resolvePromise(promise2, x, resolve, reject);
            } catch (e) {
              // 若是 onFulfilled/onRejected 拋出異常,則 promise2 必須拒絕執行,並返回拒因 e (2.2.7.2)
              reject(e);
            }
          }, 0);
        });
        this.onRejectedCallbacks.push(() => {
          // onFulfilled/onRejected 必須被異步調用,所以咱們用延時函數模擬 (2.2.4)
          setTimeout(() => {
            try {
              const x = onRejected(this.reason);
              resolvePromise(promise2, x, resolve, reject);
            } catch (e) {
              // 若是 onFulfilled/onRejected 拋出異常,則 promise2 必須拒絕執行,並返回拒因 e (2.2.7.2)
              reject(e);
            }
          }, 0);
        });
      }
    });

    // 返回 promise2 (2.2.7)
    return promise2;
  }

  // catch 實際是 then 的語法糖
  catch(fn) {
    return this.then(null, fn);
  }

  finally(fn) {
    return this.then(
      value => Promise.resolve(fn()).then(() => value),
      reason =>
        Promise.resolve(fn()).then(() => {
          throw reason;
        }),
    );
  }
}

const resolvePromise = (promise2, x, resolve, reject) => {
  // 若是 promise 和 x 指向同一個對象,將以 TypeError 做爲拒因拒絕執行 promise (2.3.1)
  if (x === promise2) {
    return reject(new TypeError('Chaining cycle detected for promise'));
  }

  // onFulfilled 和 onRejected 只能被調用一次,所以這裏加一個 flag 做爲判斷 (2.2.2.3 & 2.2.3.3)
  let isCalled = false;

  // 若是 x 是一個對象或者是一個函數 (2.3.3)
  if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
    try {
      // (2.3.3.1)
      const then = x.then;

      // 若是 then 是函數,就以 x 做爲 this 調用它 (2.3.3.2 & 2.3.3.3)
      if (typeof then === 'function') {
        // 後面接收兩個回調,第一個是成功的回調,第二個是失敗的回調 (2.3.3.3)
        then.call(
          x,
          y => {
            if (isCalled) return;
            isCalled = true;
            // 若是 resolvePromise 以 y 爲參數被調用,執行 [[Resolve]](promise, y) (2.3.3.3.1)
            resolvePromise(promise2, y, resolve, reject);
          },
          r => {
            if (isCalled) return;
            isCalled = true;
            // 若是 rejectPromise 以 r 爲緣由被調用,則以拒因 r 拒絕 promise (2.3.3.3.2)
            reject(r);
          },
        );
      } else {
        // 若是 then 不是個函數,則以 x 爲參數執行 promise (2.3.3.4)
        resolve(x);
      }
    } catch (e) {
      if (isCalled) return;
      isCalled = true;
      // 若是取 x.then 報錯,則以 e 爲拒因拒絕 `promise` (2.3.3.2)
      reject(e);
    }
  }
  // 若是 then 不是個函數或者對象,則以 x 爲參數執行 promise (2.3.4)
  else {
    resolve(x);
  }
};

// Promise.resolve
Promise.resolve = function(promises) {
  if (promises instanceof Promise) {
    return promises;
  }
  return new Promise((resolve, reject) => {
    if (promises && promises.then && typeof promises.then === 'function') {
      setTimeout(() => {
        promises.then(resolve, reject);
      });
    } else {
      resolve(promises);
    }
  });
};

// Promise.reject
Promise.reject = reason => new Promise((resolve, reject) => reject(reason));

// Promise.all
Promise.all = promises => {
  return new Promise((resolve, reject) => {
    let resolvedCounter = 0;
    let promiseNum = promises.length;
    let resolvedValues = new Array(promiseNum);
    for (let i = 0; i < promiseNum; i += 1) {
      (i => {
        Promise.resolve(promises[i]).then(
          value => {
            resolvedCounter++;
            resolvedValues[i] = value;
            if (resolvedCounter === promiseNum) {
              return resolve(resolvedValues);
            }
          },
          reason => {
            return reject(reason);
          },
        );
      })(i);
    }
  });
};

//race方法
Promise.race = promises => {
  return new Promise((resolve, reject) => {
    if (promises.length === 0) {
      return;
    } else {
      for (let i = 0, l = promises.length; i < l; i += 1) {
        Promise.resolve(promises[i]).then(
          data => {
            resolve(data);
            return;
          },
          err => {
            reject(err);
            return;
          },
        );
      }
    }
  });
};
複製代碼

最後全局安裝 yarn global add promises-aplus-tests,插入下面這段代碼,而後使用 promises-aplus-tests 該文件的文件名 來驗證你手寫的 Promise 是否符合 Promises A+ 規範。

Promise.defer = Promise.deferred = function() {
  let dfd = {};
  dfd.promise = new Promise((resolve, reject) => {
    dfd.resolve = resolve;
    dfd.reject = reject;
  });
  return dfd;
};
module.exports = Promise;
複製代碼

Promises/A+ 規範

一個開放、可靠且通用的 JavaScript Promise 標準。由開發者制定,供開發者參考。

promise 表明着一個異步操做的最終結果,與之交互的主要方式是它的 then 方法,該方法註冊了兩個回調函數,用於接收 promise 最終的值或者失敗的緣由。

該規範詳細描述了 then 方法的行爲,全部遵循 Promises/A+ 規範實現的 promise 都可以本標準做爲參照基礎來實施。所以,這份規範是很穩定的。雖然 Promises/A+ 組織偶爾會修訂這份規範,但大可能是爲了處理一些特殊的邊界狀況。這些改動都是微小且向下兼容的。若是咱們要進行大規模不兼容的更新,咱們必定會在事先進行謹慎地考慮、詳盡的探討和嚴格的測試。

最後,核心的 Promises/A+ 規範不會提供如何建立、解決和拒絕 promise,而是專一於提供一個通用的 then 方法。上述對於 promises 的操做方法未來在其餘規範中可能會說起。

1. 術語

1.1. 'promise' 是一個擁有 then 方法的對象或者函數,且其行爲符合此規範。

1.2. 'thenable' 是一個用來定義 then 方法的對象或者函數。

1.3. 'value' 是任何一個合法的 JavaScript 值 (包括 undefined,thenable 或者 promise)

1.4. 'exception' 是一個使用 throw 語句拋出的值

1.5. 'reason' 代表了一個 promise 爲何會被拒絕

2. 要求

2.1. Promise 狀態

promise 必須是三個狀態之一:等待態(Pending)、執行態(Fulfilled)和拒絕態(Rejected)。

  • 2.1.1. 當前狀態爲 pending 時,一個 promise:

    • 2.1.1.1 能夠轉換成 fulfilled 或者 rejected 狀態
  • 2.1.2. 當前狀態爲 fulfilled 時,一個 promise:

    • 2.1.2.1 不能再轉換成其餘任何狀態

    • 2.1.2.2 必須擁有一個不可變的值

  • 2.1.3. 當前狀態爲 rejected 時,一個 promise:

    • 2.1.3.1 不能再轉換成其餘任何狀態

    • 2.1.3.2 必須擁有一個不可變的緣由

這裏的不可變指的是恆等(便可用 === 判斷相等),而不是意味着更深層次的不可變。(即當 value 或者 reason 爲引用類型時,只要求引用地址相等便可,但屬性值能夠被修改)

2.2. then 方法

promise 必須提供一個 then 方法以訪問它當前或最終的值或被拒絕的緣由。

一個 promise 的 then 方法接收兩個參數:

promise.then(onFulfilled, onRejected);
複製代碼
  • 2.2.1 onFulfilledonRejected 都是可選參數。

    • 2.2.1.1 若是 onFulfilled 不是個函數,它將被忽略

    • 2.2.1.2 若是 onRejected 不是個函數,它將被忽略

  • 2.2.2 若是 onFulfilled 是一個函數:

    • 2.2.2.1 它必須在 promise 完成式後被調用,而且以 promise 的值做爲它的第一個參數。

    • 2.2.2.2 在 promise 未完成前不可調用

    • 2.2.2.3 此函數僅可調用一次

  • 2.2.3 若是 onRejected 是一個函數:

    • 2.2.3.1 它必須在 promise 被拒絕後被調用,而且以 promise 的緣由做爲它的第一個參數。

    • 2.2.3.2 在 promise 未被拒絕前不可調用

    • 2.2.3.3 此函數僅可調用一次

  • 2.2.4 onFulfilledonRejected 只有在 執行上下文 堆棧僅包含平臺代碼時纔可被調用。[1]

  • 2.2.5 onFulfilledonRejected 必須被做爲函數調用 (即沒有 this 值)。[2]

  • 2.2.6 then 在相同的 promise 能夠被調用屢次

    • 2.2.6.1 當 promise 是完成態, 全部相應的 onFulfilled 回調必須按其原始調用的順序執行。

    • 2.2.6.2 當 promise 是拒絕態,全部相應的 onRejected 回調必須按其原始調用的順序執行。

  • 2.2.7 每一個 then 方法必須返回一個 promise [3]

    promise2 = promise1.then(onFulfilled, onRejected);
    複製代碼
    • 2.2.7.1 若是 onFulfilled 或者 onRejected 返回一個值 x ,則運行下面的 Promise 解決過程:[[Resolve]](promise2, x)

    • 2.2.7.2 若是 onFulfilled 或者 onRejected 拋出一個異常 e ,則 promise2 必須拒絕執行,並返回拒因 e

    • 2.2.7.3 若是 onFulfilled 不是函數且 promise1 成功執行, promise2 必須成功執行並返回相同的值

    • 2.2.7.4 若是 onRejected 不是函數且 promise1 拒絕執行, promise2 必須拒絕執行並返回相同的拒因

2.3. Promise 解決過程

Promise 解決過程是一個抽象的操做,它接收一個 promise 和一個值,咱們能夠表示爲 [[Resolve]](promise, x),若是 x 是一個 thenable 的對象,解決程序將試圖接受 x 的狀態,不然用 x 的值來執行 promise

這種對 thenales 的處理使得 promise 的實現更加有普適性,只要它暴露出一個兼容 Promises/A+ 規範的 then 方法。它還容許讓遵循 Promise/A+ 規範的實現和不太規範但可用的實現良好共存。

爲了運行 [[Resolve]](promise, x),要執行下面的步驟:

  • 2.3.1 若是 promisex 指向同一個對象,將以 TypeError 做爲拒因拒絕執行 promise

  • 2.3.2 若是 x 是一個 promise,那麼將 promise 將接受它的狀態 [4]

    • 2.3.2.1 若是 x 是等待態,promise 必須保留等待狀態直到 x 被完成或者被拒絕。

    • 2.3.2.2 若是 x 是完成態,用相同的值執行 promise

    • 2.3.2.3 若是 x 是拒態,用相同的緣由拒絕 promise

  • 2.3.3 若是 x 是一個對象或者是一個函數,

    • 2.3.3.1 把 x.then 賦值給 then[5]

    • 2.3.3.2 若是取 x.then 的值時拋出錯誤 e,則以 e 爲拒因拒絕 promise

    • 2.3.3.3 若是 then 是函數,將 x 做爲函數的做用域 this 來調用它。傳遞兩個回調函數做爲參數,第一個參數叫作 resolvePromise,第二個參數叫作 rejectPromise:

      • 2.3.3.3.1 若是 resolvePromisey 爲參數被調用,執行 [[Resolve]](promise, y)

      • 2.3.3.3.2 若是 rejectPromiser 爲緣由被調用,則以拒因 r 拒絕 promise

      • 2.3.3.3.3 若是 resolvePromiserejectPromise 都被調用,或者被同一參數調用了屢次,則優先採用首次調用並忽略剩下的調用。

      • 2.3.3.3.4 若是調用 then 拋出一個異常 e

        • 2.3.3.3.4.1 若是 resolvePromiserejectPromise 都被調用,則忽略掉它

        • 2.3.3.3.4.2 不然,以 e 爲拒因拒絕這個 promise

    • 2.3.3.4 若是 then 不是個函數,則以 x 爲參數執行 promise

  • 2.3.4 若是 then 不是個函數或者對象,則以 x 爲參數執行 promise

若是一個 promise 被一個循環的 thenable 鏈中的對象解決,而 [[Resolve]](promise, thenable) 的遞歸性質又使得其被再次調用,根據上述的算法將會陷入無限遞歸之中。算法雖不強制要求,但也鼓勵施者檢測這樣的遞歸是否存在,若檢測到存在則以一個可識別的 TypeError 爲拒因來拒絕 promise [6]

3. 註釋


  1. 這裏的「平臺代碼」意味着引擎,環境和 promise 實施代碼,在實踐中要確保 onFulfilledonRejected 異步執行,且應該在 then 方法被調用的那一輪事件循環以後的新執行棧中執行。這個事件隊列能夠採用「宏任務(macro-task)」機制,相似於 setTimeOut 或者 setImmediate,也可使用「微任務(micro-task)」機制來實現,相似於 MutationObserverprocess.nextTick。由於 promise 實現被認爲是平臺代碼,因此它自己可能包含一個任務調度隊列或跳板,在其中調用處理程序。 ↩︎

  2. 在嚴格模式下 thisundefined,而在非嚴格模式中,this 爲全局對象。 ↩︎

  3. 代碼實如今知足全部要求的狀況下能夠容許 promise2 === promise1 。每一個實現都要文檔說明其是否容許以及在何種條件下容許 promise2 === promise1↩︎

  4. 整體來講,若是 x 符合當前實現,咱們才認爲它是真正的 promise 。這一規則容許那些特例實現接受符合已知要求的 Promises 狀態。 ↩︎

  5. 這步咱們先是存儲了一個指向 x.then 的引用,而後測試並調用該引用,以免屢次訪問 x.then 屬性。這種預防措施確保了該屬性的一致性,由於其值可能在檢索調用時被改變。 ↩︎

  6. 實現不該該對 thenable 鏈的深度設限,並假定超出本限制的遞歸就是無限循環。只有真正的循環遞歸才應能致使 TypeError 異常;若是一條無限長的鏈上 thenable 均不相同,那麼遞歸下去永遠是正確的行爲。 ↩︎

相關文章
相關標籤/搜索