來,實現一個 A+ 規範的 Promise 吧

前言

在前端開發中,Promise 是一個特別重要的概念,不少的異步操做都依賴於 Promise。既然在平常中和它打過那麼多的交道,那麼咱們來本身實現一個 Promise,加深對 Promise 的理解,加強本身的 JavaScript 功力。javascript

本次在實現 Promise 的同時會使用 jest 編寫測試用例,以保證明現過程的正確性。前端

若是想看測試框架的搭建或者完整實現的,能夠點擊個人github 倉庫進行查看,若是你喜歡,歡迎 star,若是你發現個人錯誤,歡迎提出來。java

A+規範

這是一份開放、健全且通用的 Promise 實現規範。由開發者制定,供開發者參考。git

這裏是官方規範,照着官方規範去實現,就能夠寫一個屬於本身的、符合標準的 Promisegithub

實現

話很少說,咱們來開始根據 A+ 規範實現 Promise數組

規範的第一節是對一些術語的表達,並沒有實際功能無需實現。promise

1. 聲明 Promise 類

Promise 是一個類(JavsScript 的類是用函數實現的,只是一個語法糖),它必須接受一個函數,不然報錯;它還有一個 then 方法。框架

class PROMISE {
  constructor(executor) {
    if (typeof executor !== 'function') {
      throw new TypeError(`Promise resolver ${executor} is not a function`);
    }
  }
  then() {}
}
複製代碼

測試用例以下:異步

import Promise from '../';

describe('Promise', () => {
  test('是一個類', () => {
    expect(Promise).toBeInstanceOf(Function);
    expect(Promise.prototype).toBeInstanceOf(Object);
  });
  test('new Promise() 必須接受一個函數', () => {
    expect(() => {
      // @ts-ignore
      new Promise();
    }).toThrow(TypeError);
    expect(() => {
      // @ts-ignore
      new Promise('promise');
    }).toThrow(TypeError);
    expect(() => {
      // @ts-ignore
      new Promise(null);
    }).toThrow(TypeError);
  });
  test('new Promise(fn) 會生成一個對象,對象有 then 方法', () => {
    const promise = new Promise(() => {});
    expect(promise.then).toBeInstanceOf(Function);
  });
})
複製代碼

測試用例我後面再也不列出來了,有興趣的能夠去個人 github 倉庫查看。函數

2.狀態維護

Promise 有三種狀態:請求態(pending)、完成態(fulfilled)和拒絕態(rejected)。Promise 一開始是請求態,它能夠轉爲另外兩種狀態(只容許改變一次),它會在狀態改變的時候獲得一個值。

class PROMISE {
  constructor(executor) {
    if (typeof executor !== 'function') {
      throw new TypeError(`Promise resolver ${executor} is not a function`);
    }
    // 初始狀態
    this.status = 'pending';
    // 初始值
    this.value = null;
    // class 內部默認是嚴格模式,因此須要綁定 this
    executor(this.resolve.bind(this), this.reject.bind(this));
  }
  then() {}
  resolve(value) {
    // 狀態保護
    if (this.status !== 'pending') {
      return;
    }
    // 改變狀態, 賦值
    this.status = 'fulfilled';
    this.value = value;
  }
  reject(reason) {
    // 狀態保護
    if (this.status !== 'pending') {
      return;
    }
    // 改變狀態, 賦值
    this.status = 'rejected';
    this.value = reason;
  }
}
複製代碼

到這裏,咱們就差很少實現了規範 2.1。

3.then

then 能夠接受兩個函數,會在狀態改變以後異步執行,根據規範 2.2.1,若是它們不是函數,就忽略。then 的異步本是一個微任務,這裏用宏任務 setTimeout 就將代替一下(若是你想了解微任務、宏任務的知識,歡迎點這裏查看我寫的關於 Event Loop 的文章)。

then(onFulfilled, onRejected) {
  // 暫時將它們變成空函數,後面再作修改
  if (typeof onFulfilled !== 'function') {
    onFulfilled = () => {};
  }
  if (typeof onRejected !== 'function') {
    onRejected = () => {};
  }
  if (this.status === 'fulfilled') {
    // 異步執行,將就用 setTimeout 實現
    setTimeout(() => {
      onFulfilled(this.value);
    });
  }
  if (this.status === 'rejected') {
    setTimeout(() => {
      onRejected(this.value);
    });
  }
}
複製代碼

這裏的狀況是期待執行 then 函數時,Promise 的狀態已經獲得了改變。

若是 Promise 的執行函數是一個異步函數,執行 then 的時候,Promise 的狀態還沒獲得改變,那麼就須要把 then 接受的兩個函數保存起來,等到 resolvereject 的時候執行,這裏也要異步執行。

then(onFulfilled, onRejected) {
  // 暫時將它們變成同步的空函數,後面再作修改
  if (typeof onFulfilled !== 'function') {
    onFulfilled = () => {};
  }
  if (typeof onRejected !== 'function') {
    onRejected = () => {};
  }
  // 若是執行 then 的時候,Promise 狀態還未發生變化,就先將這兩個函數存起來
  if (this.status === 'pending') {
    this.callbacks.push({
      onFulfilled: () => {
        setTimeout(() => {
          onFulfilled();
        });
      },
      onRejected: () => {
        setTimeout(() => {
          onRejected();
        });
      }
    });
  }
  if (this.status === 'fulfilled') {
    // 異步執行,將就用 setTimeout 實現
    setTimeout(() => {
      // 2.2.5
      onFulfilled.call(undefined, this.value);
    });
  }
  if (this.status === 'rejected') {
    setTimeout(() => {
      // 2.2.5
      onRejected.call(undefined, this.value);
    });
  }
}
resolve(value) {
  // 狀態保護
  if (this.status !== 'pending') {
    return;
  }
  // 改變狀態, 賦值
  this.status = 'fulfilled';
  this.value = value;
  // 若是回調函數數組中有值,說明以前執行過 then,須要調用 then 接受的函數
  this.callbacks.forEach((callback) => {
    // 2.2.5
    callback.onFulfilled.call(undefined, value);
  });
}
reject(reason) {
  // 狀態保護
  if (this.status !== 'pending') {
    return;
  }
  // 改變狀態, 賦值
  this.status = 'rejected';
  this.value = reason;
  // 若是回調函數數組中有值,說明以前執行過 then,須要調用 then 接受的函數
  this.callbacks.forEach((callback) => {
    // 2.2.5
    callback.onRejected.call(undefined, reason);
  });
}
複製代碼

這樣一來,規範的 2.2.2 和 2.2.3 就都實現了。

並且因爲 then 裏面的 onFulfilledonRejected 都是異步執行的,因此它也知足規範 2.2.4,它會在 Promise 的代碼執行以後被調用。

根據規範 2.2.5, onFulfilledonRejected 調用時也不存在 this ,因此用 .call 調用,指定 undefinedthis

規範 2.2.6,若是 then 執行以前 Promise 已經改變了狀態,那麼直接執行多個 then。不然將 then 的函數參數存在 callbacks 數組中,後面依次調用,實現規範 2.2.6。

4. 鏈式操做

Promise 有一個 then 方法,then 以後還能夠 then,那麼讓 then 返回一個 Promise 便可,根據規範 2.2.7,咱們也必須讓 then 返回一個 Promise

根據規範 2.2.7.1,不管是 onFulfilledonrejected,它們返回的值都會被當作 then 返回的新的 Promiseresolve 的值成功處理。

根據規範 2.2.7.2,若是 onFulfilledonRejected 拋出了一個錯誤 e,那麼會被當作 then 返回的新的 Promisereject 的值失敗處理。

then(onFulfilled, onRejected) {
  // then 返回一個 Promise
  return new PROMISE((resolve, reject) => {
    // 暫時將它們變成空函數,後面再作修改
    if (typeof onFulfilled !== 'function') {
      onFulfilled = () => {};
    }
    if (typeof onRejected !== 'function') {
      onRejected = () => {};
    }
    // 若是執行 then 的時候,Promise 狀態還未發生變化,就先將這兩個函數存起來
    if (this.status === 'pending') {
      this.callbacks.push({
        // 這裏也須要變了
        onFulfilled: () => {
            setTimeout(() => {
              try {
                // 2.2.5
                const result = onFulfilled.call(undefined, this.value);
                // onFulfilled 的返回值當作新的 Promise 的 resolve 的值被調用
                resolve(result);
              } catch (error) {
                // 若是拋出了錯誤,那麼當作新的 Promise 的 reject 的值被調用
                reject(error);
              }
            });
          },
          onRejected: () => {
            setTimeout(() => {
              try {
                // 2.2.5
                const result = onRejected.call(undefined, this.value);
                // onRejected 的返回值當作新的 Promise 的 resolve 的值被調用
                resolve(result);
              } catch (error) {
                // 若是拋出了錯誤,那麼當作新的 Promise 的 reject 的值被調用
                reject(error);
              }
            });
          }
      });
    }
    if (this.status === 'fulfilled') {
      // 異步執行,將就用 setTimeout 實現
      setTimeout(() => {
        try {
          // 2.2.5
          const result = onFulfilled.call(undefined, this.value);
          // onFulfilled 的返回值當作新的 Promise 的 resolve 的值被調用
          resolve(result);
        } catch (error) {
          // 若是拋出了錯誤,那麼當作新的 Promise 的 reject 的值被調用
          reject(error);
        }
      });
    }
    if (this.status === 'rejected') {
      setTimeout(() => {
        try {
          // 2.2.5
          const result = onRejected.call(undefined, this.value);
          // onRejected 的返回值當作新的 Promise 的 resolve 的值被調用
          resolve(result);
        } catch (error) {
          // 若是拋出了錯誤,那麼當作新的 Promise 的 reject 的值被調用
          reject(error);
        }
      });
    }
  });
}
複製代碼

規範 2.2.7.3 和 2.2.7.4 表示若是 thenonFulfilledonRejected 不是函數,那麼新的 Promise 會用上一個 Promise 成功(resolve)或失敗(reject)的值繼續成功或失敗,也就是會繼承上一個 Promise 的狀態和值。

舉一個例子

new Promise((resolve, reject) => {
  /** * 執行函數體 */
})
  .then()
  .then(
    function A() {},
    function B() {}
  );
複製代碼

由於第一個 then 的參數不是函數,那麼會發生穿透傳遞,因此後一個 then 接受的兩個參數 function A 和 function B,會根據最前面那個 Promise 的狀態和值來進行調用。

也就是上面的代碼其實和下面的代碼執行結果同樣。

new Promise((resolve, reject) => {
  /** * 執行函數體 */
})
  .then(
    function A() {},
    function B() {}
  );
複製代碼

好的,讓咱們來實現這個規範。

其實很簡單,你上一個 Promise 若是是 resolve 時,那麼我用 thenPromiseresolve,且值不變,若是是 reject,那麼 thenPromisereject,且值不變。

這裏稍微一點點繞,但願你把這裏仔細看,徹底搞明白。

if (typeof onFulfilled !== 'function') {
  onFulfilled = (value) => {
    // 前面的 Promise 是 resolve 時,會調用 onFulfilled
    // 那麼 then 的新 Promise 也 resolve
    // 將狀態和值傳遞給 then 的 then
    resolve(value);
  };
}
if (typeof onRejected !== 'function') {
  onRejected = (reason) => {
    // 前面的 Promise 是 reject 時,會調用 onRejected
    // 那麼 then 的新 Promise 也 reject
    // 將狀態和值傳遞給 then 的 then
    reject(reason);
  };
}
複製代碼

其實這裏能夠簡化一下,變成下面這種。

if (typeof onFulfilled !== 'function') {
  onFulfilled = resolve;
}
if (typeof onRejected !== 'function') {
  onRejected = reject;
}
複製代碼

這樣,Promise 的鏈式操做就完成了。

5.實現 2.3.1

接下來咱們繼續看規範 2.3 的部分。

若是 Promiseresolve 或者 reject 調用的值是同一個,那麼應該使 Promise 處於拒絕(reject )態,值爲 TypeError

代碼以下:

constructor(executor) {
  if (typeof executor !== 'function') {
    throw new TypeError(`Promise resolver ${executor} is not a function`);
  }
  // 初始狀態
  this.status = 'pending';
  // 初始值
  this.value = null;
  // 初始回調數組
  this.callbacks = [];
  // class 內部默認是嚴格模式,因此須要綁定 this
  try {
    executor(this.resolve.bind(this), this.reject.bind(this));
  } catch (error) {
    // 接住 resolve 和 reject 拋出的 TypeError,做爲 reject 的值調用
    this.reject(error);
  }
}
/** * 代碼 */
resolve(value) {
  // 狀態保護
  if (this.status !== 'pending') {
    return;
  }
  // 若是 promise 和 resolve 調用的值是同一個,那麼就拋出錯誤
  if (value === this) {
    throw new TypeError();
  }
  // 改變狀態, 賦值
  this.status = 'fulfilled';
  this.value = value;
  // 若是回調函數數組中有值,說明以前執行過 then,須要調用 then 接受的函數
  this.callbacks.forEach((callback) => {
    callback.onFulfilled.call(undefined, value);
  });
}
reject(reason) {
  // 狀態保護
  if (this.status !== 'pending') {
    return;
  }
  // 若是 promise 和 reject 調用的值是同一個,那麼就拋出錯誤
  if (value === this) {
    throw new TypeError();
  }
  // 改變狀態, 賦值
  this.status = 'rejected';
  this.value = reason;
  this.callbacks.forEach((callback) => {
    callback.onRejected.call(undefined, reason);
  });
}
複製代碼

6.實現 2.3.3

規範 2.3.2 是 2.3.3 狀況的一個子集,咱們直接實現 2.3.3 就能夠了。

規範 2.3.3 說了那麼多,其實就是在 resolvereject 中添加下面幾行代碼。

if (value instanceof Object) {
  // 2.3.3.1 2.3.3.2
  const then = value.then;
  // 2.3.3.3
  if (typeof then === 'function') {
    return then.call(
      value,
      this.resolve.bind(this),
      this.reject.bind(this)
    );
  }
}
複製代碼

關於規範 2.3.3.2,我理解的是,並非說 x.then 是一個異常,而是在取值的過程當中發生了一個異常,代碼表達以下:

// 不是這種
const X = {
  then: new Error()
}
// 是相似這種的狀況
const x = {};
Object.defineProperty(x, 'then', {
  get: function() {
    throw new Error('y');
  }
});

new Promise((resolve, reject) => {
  resolve(x)
}).then((value) => {
  console.log('fulfilled', value)
}, (reason) => {
  console.log('rjected', reason)
})
複製代碼

因爲取值 x.then 的過程當中拋出了一個異常,被 constructor 中的 try catch 捕捉到了,執行 reject,這裏就無需作處理了。

規範 2.3.4 不用特殊實現,說的就是正常狀況。

7.完整實現

到這裏就把 A+ 規範走了一遍,實現的 Promise 以下:

class PROMISE {
  constructor(executor) {
    if (typeof executor !== 'function') {
      throw new TypeError(`Promise resolver ${executor} is not a function`);
    }
    // 初始狀態
    this.status = 'pending';
    // 初始值
    this.value = null;
    // 初始回調數組
    this.callbacks = [];
    // class 內部默認是嚴格模式,因此須要綁定 this
    try {
      executor(this.resolve.bind(this), this.reject.bind(this));
    } catch (error) {
      // 接住 resolve 和 reject 拋出的 TypeError,做爲 reject 的值調用
      this.reject(error);
    }
  }
  then(onFulfilled, onRejected) {
    // then 返回一個 Promise
    return new PROMISE((resolve, reject) => {
      // then 的穿透傳遞
      if (typeof onFulfilled !== 'function') {
        onFulfilled = resolve;
      }
      if (typeof onRejected !== 'function') {
        onRejected = reject;
      }
      // 若是執行 then 的時候,Promise 狀態還未發生變化,就先將這兩個函數存起來
      if (this.status === 'pending') {
        this.callbacks.push({
          onFulfilled: () => {
            setTimeout(() => {
              try {
                const result = onFulfilled.call(undefined, this.value);
                // onFulfilled 的返回值當作新的 Promise 的 resolve 的值被調用
                resolve(result);
              } catch (error) {
                // 若是拋出了錯誤,那麼當作新的 Promise 的 reject 的值被調用
                reject(error);
              }
            });
          },
          onRejected: () => {
            setTimeout(() => {
              try {
                const result = onRejected.call(undefined, this.value);
                // onRejected 的返回值當作新的 Promise 的 resolve 的值被調用
                resolve(result);
              } catch (error) {
                // 若是拋出了錯誤,那麼當作新的 Promise 的 reject 的值被調用
                reject(error);
              }
            });
          }
        });
      }
      if (this.status === 'fulfilled') {
        // 異步執行,將就用 setTimeout 實現
        setTimeout(() => {
          try {
            const result = onFulfilled.call(undefined, this.value);
            // onFulfilled 的返回值當作新的 Promise 的 resolve 的值被調用
            resolve(result);
          } catch (error) {
            // 若是拋出了錯誤,那麼當作新的 Promise 的 reject 的值被調用
            reject(error);
          }
        });
      }
      if (this.status === 'rejected') {
        setTimeout(() => {
          try {
            const result = onRejected.call(undefined, this.value);
            // onRejected 的返回值當作新的 Promise 的 resolve 的值被調用
            resolve(result);
          } catch (error) {
            // 若是拋出了錯誤,那麼當作新的 Promise 的 reject 的值被調用
            reject(error);
          }
        });
      }
    });
  }
  resolve(value) {
    // 狀態保護
    if (this.status !== 'pending') {
      return;
    }
    // 若是 promise 和 resolve 調用的值是同一個,那麼就拋出錯誤
    if (value === this) {
      throw new TypeError('Chaining cycle detected for promise');
    }
    if (value instanceof Object) {
      // 2.3.3.1
      const then = value.then;
      // 2.3.3.3
      if (typeof then === 'function') {
        return then.call(
          value,
          this.resolve.bind(this),
          this.reject.bind(this)
        );
      }
    }
    // 改變狀態, 賦值
    this.status = 'fulfilled';
    this.value = value;
    // 若是回調函數數組中有值,說明以前執行過 then,須要調用 then 接受的函數
    this.callbacks.forEach((callback) => {
      callback.onFulfilled.call(undefined, value);
    });
  }
  reject(reason) {
    // 狀態保護
    if (this.status !== 'pending') {
      return;
    }
    // 若是 promise 和 reject 調用的值是同一個,那麼就拋出錯誤
    if (reason === this) {
      throw new TypeError('Chaining cycle detected for promise');
    }
    if (reason instanceof Object) {
      // 2.3.3.1
      const then = reason.then;
      // 2.3.3.3
      if (typeof then === 'function') {
        return then.call(
          reason,
          this.resolve.bind(this),
          this.reject.bind(this)
        );
      }
    }
    // 改變狀態, 賦值
    this.status = 'rejected';
    this.value = reason;
    this.callbacks.forEach((callback) => {
      callback.onRejected.call(undefined, reason);
    });
  }
}
複製代碼

其中重複的代碼我在這裏就不抽離出來了,這樣方便閱讀。

8.靜態方法

resolverejectallrace這幾個靜態方法其實不屬於 A+ 規範中,我這裏也順帶實現一下。

resolvereject 相似,接受一個值,返回一個 Promise。若是接受的值是一個 Promise,那麼就繼承該 Promise 的狀態和值。

static resolve(value) {
  return new PROMISE((resolve, reject) => {
    if (value instanceof PROMISE) {
      value.then(resolve, reject);
    } else {
      resolve(value);
    }
  });
}
static reject(reason) {
  return new PROMISE((resolve, reject) => {
    if (reason instanceof PROMISE) {
      reason.then(resolve, reject);
    } else {
      reject(reason);
    }
  });
}
複製代碼

all 是接受一個 Promise 數組,返回一個 Promise

這裏定義一個 results 數組,而後遍歷 Promise 數組,每 resolve 一個 Promise ,就像 results 加入一個 resolve 的值,若是results 的長度與 Promise 數組的長度相同,那麼說明所有的 Promiseresolve 了,那麼 all 返回的 Promiseresolve 這個數組。

另外,只要 Promise 數組中有一個 Promise 轉爲了 reject,那麼 all 返回的 Promisereject掉。

static all(promiseArray) {
  return new PROMISE((resolve, reject) => {
    const results = [];
    promiseArray.forEach((promise) => {
      promise.then((value) => {
        results.push(value);
        if (results.length === promiseArray.length) {
          resolve(results);
        }
      }, reject);
    });
  });
}
複製代碼

race 也是接受一個 Promise 數組,返回一個 Promise

只有 Promise 數組中有一個 Promise resolve 或者 reject 了,那麼 race 返回的 Promiseresolve 或者 reject

static race(promiseArray) {
  return new PROMISE((resolve, reject) => {
    promiseArray.forEach((promise) => {
      promise.then(resolve, reject);
    });
  });
}
複製代碼

感想

從頭實現一遍 Promise 的 A+ 規範的過程當中,對 Promise 的一些細枝細節都梳理了一遍,一些以前根本沒有注意到的地方也給暴露出來了,特別是 x 若是是一個有 then 方法的對象,那麼 x 會被包裝成一個 Promise,這個地方也是以前沒有接觸到的。

這個過程我用 TypeScript 實現了一遍,具體代碼點這裏,其中也包括了我寫的測試用例,若是你喜歡,歡迎 star。

若是你發現我實現過程有不對的地方,歡迎與我探討。

相關文章
相關標籤/搜索