從零實現一個Promise
一、Promise/A+ 規范
① Promise 是一個類或者函數,內部擁有3個狀態,分別為pending(等待)、fulfilled(執行、完成)、rejected(拒絕、未完成)。
默認為pending狀態,即Promise對象剛創建的時候狀態為pending,并且pending狀態可以轉換fulfilled或者rejected。
fulfilled和rejected為最終的狀態,一旦變為fulfilled或者rejected,那么將無法轉變為其他狀態。
② Promise需要對外提供一個then方法。
- promise.then(onFulfilled, onRejected)
如果可選參數onFulfilled和onRejected不為函數時應該被忽略;
onFulfilled和onRejected函數都應該是異步執行的;
當調用 onFulfilled 函數時,會將當前 Promise 的值作為參數傳入,并且只能調用一次;
當調用 onRejected 函數時,會將當前 Promise 的失敗原因作為參數傳入,并且只能調用一次;
then函數的返回值仍然為Promise,以便進行鏈式調用;
③ resolvePromisethen方法會創建并返回一個Promise對象,then中注冊的回調函數會返回各種值,必須進行校驗。
then方法返回的promise不能與then中回調函數返回值x相等,否則需要拋出錯誤;
如果是then回調函數返回值為一個非Promise對象,則直接用then返回的promise對象的resolve方法,resolve(x)即可。
如果then回調函數返回值x為一個Promise對象或者一個帶then方法的對象或函數,那么需要執行其then方法注冊回調,拿到Promise或類Promise對象的值作為then返回的promise的值,如果值仍然為Promise對象則需要進行遞歸操作;
二、實現Promise
① 根據第一條規范,Promise是一個類或者函數,所以我們先將Promise定義成一個類,同時內部有三個狀態,我們將其定義為常量。
- var PENDING = "pending"; // 等待狀態
- var FULFILLED = "fulfilled"; // 執行、完成狀態
- var REJECTED = "rejected"; // 拒絕、未完成狀態
- class Promise {
- constructor() {
- this.state = PENDING; // Promise對象創建完成后默認為等待狀態
- }
- }
② 我們在創建Promise的時候會傳入一個函數,該函數會在創建Promise對象的時候立即執行,并且會接收兩個參數,分別用于執行或拒絕當前Promise對象,即修改當前Promise對象的狀態。Promise是用于處理異步的,所以在Promise狀態變為完成的時候可能會接收到異步操作執行的結果,在Promise狀態變為未完成的時候可能會接收到失敗的原因,所以Promise內部還需要保存異步操作的結果value、失敗的原因reason。
- ......
- class Promise {
- constructor(executor) { // 傳入執行器函數
- ......
- this.value = undefined; // 保存異步操作的結果
- this.reason = undefined; // 保存失敗的原因
- const resolve = (value) => {
- this.value = value;
- this.state = FULFILLED; // 將Promise對象的狀態改為完成狀態
- }
- const reject = (reason) => {
- this.reason = reason;
- this.state = REJECTED; // 將Promise對象的狀態改為未完成狀態
- }
- try {
- executor(resolve, reject); // 執行器由用戶傳入可能會發生錯誤,所以需要進行捕獲
- } catch(e) {
- reject(e);
- }
- }
- }
③ 這里還存在一個問題,就是Promise必須是單次執行的,Promise的狀態一旦從pending狀態修改為fulfilled或者rejected,就不能再發生變化,從fulfilled變為fulfilled也不可以,也就是說resolve或者reject只能執行一次。所以我們需要對resolve和reject內部進行判斷,如果狀態已經變化了則不再執行了,如:
- ......
- class Promise {
- constructor(executor) { // 傳入執行器函數
- ......
- const resolve = (value) => {
- if (this.state === PENDING) { // 防止用戶多次resolve,以第一次resolve為準
- ......
- }
- }
- const reject = (reason) => {
- if (this.state === PENDING) { // 防止用戶多次reject
- ......
- }
- }
- ......
- }
- }
④ 給Promise添加一個then函數,then函數接收onFulfilled, onRejected兩個函數作為參數,分別用于處理Promise完成時和未完成時的回調函數,如果不是函數,則要進行初始化為一個函數,如:
- class Promise {
- then(onFulfilled, onRejected) {
- onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (value) => { // 如果onFulfilled不是函數,則初始化一個完成處理函數
- return value;
- };
- onRejected = typeof onRejected === "function" ? onRejected : (reason) => { // 如果onRejected不是函數,則初始化一個未完成處理函數
- throw reason; // 傳什么就拋出什么
- }
- }
- }
⑤ then方法其實就是一個注冊回調的過程,當調用then的這個Promise對象的狀態變為完成狀態就可以執行onFulfilled回調函數,當Promise對象的狀態變為拒絕狀態就可以執行onRejected回調函數了。所以回調函數的執行依賴于調用then的Promise的狀態。同時為了支持鏈式調用,then方法還需要返回一個Promise對象。根據前面的Promise規范,傳入的回調函數必須異步執行,這里用setTimeout進行模擬。
- class Promise {
- then(onFulfilled, onRejected) {
- ......
- let promise;
- switch(this.state) {
- case FULFILLED: // 調用then方法的時候,當前Promise狀態已經變成完成狀態,則可用立即執行完成的回調函數
- promise = new Promise((resolve, reject) => {
- setTimeout(() => {
- try {
- let x = onFulfilled(this.value);
- } catch(e) {
- console.log(e); // 打印錯誤信息
- reject(e);
- }
- });
- });
- break;
- case REJECTED:
- promise = new Promise((resolve, reject) => {
- setTimeout(() => {
- try {
- let x = onRejected(this.reason);
- } catch(e) {
- reject(e);
- }
- });
- }
- break;
- case PENDING:
- promise = new Promise((resolve, reject) => {
- // TODO
- });
- break;
- }
- return promise;
- }
- }
⑥ 當調用then的Promise對象處于pending狀態的時候,此時通過then注冊的回調函數不能立即執行,必須等待Promise的狀態變為最終狀態才能執行注冊的回調函數。這里就涉及到了一個發布訂閱模式。我們可以先將回調函數保存起來,那么什么時候Promise才會變成最終狀態呢?那就是調用resolve或reject的時候,所以我們可以在調用resolve或reject的時候,取出注冊的回調函數然后執行即可。
- class Promise {
- constructor(executor) {
- const resolve = (value) => {
- if (this.state === PENDING) { // 防止用戶多次resolve,以第一次resolve為準
- ......
- this.onFulfilleds.forEach(fn => fn()); // 取出then中注冊的完成回調函數并執行
- }
- };
- const reject = (reason) => {
- if (this.state === PENDING) { // 防止用戶多次reject
- ......
- this.onRejecteds.forEach(fn => fn()); // 取出then中注冊的拒絕回調函數并執行
- }
- };
- }
- then(onFulfilled, onRejected) {
- ......
- switch(this.state) {
- case PENDING:
- promise = new Promise((resolve, reject) => {
- this.onFulfilleds.push(() => {
- try {
- let x = onFulfilled(this.value);
- } catch(e) {
- console.log(e); // 打印錯誤信息
- reject(e);
- }
- });
- this.onRejecteds.push(() => {
- try {
- let x = onRejected(this.reason);
- } catch(e) {
- reject(e);
- }
- });
- });
- break;
- }
- }
- }
⑦ 接下來就是要處理then注冊的回調函數的返回值了,因為回調函數的返回值可能是各種各樣的情況,可能是普通的值,可能是Promise對象,也可能是帶then方法的對象,所以我們要一一進行處理。這里我們使用一個單獨的方法resolvePromise()進行各種情況的處理,如:
- // 傳入then()方法中創建的Promise對象,回調函數的返回值x,then()方法中創建的Promise的resolve、reject
- const resolvePromise = function(promise, x, resolve, reject) {
- // TODO
- }
- class Promise {
- constructor(executor) { // 傳入執行器函數
- ......
- }
- then(onFulfilled, onRejected) {
- case FULFILLED:
- promise = new Promise((resolve, reject) => {
- ......
- let x = onFulfilled(this.value);
- resolvePromise(promise, x, resolve, reject); // 處理回調函數的返回值
- });
- case REJECTED:
- promise = new Promise((resolve, reject) => {
- ......
- let x = onRejected(this.reason);
- resolvePromise(promise, x, resolve, reject); // 處理回調函數的返回值
- });
- case PENDING:
- this.onFulfilleds.push(() => {
- let x = onFulfilled(this.value);
- resolvePromise(promise, x, resolve, reject); // 處理回調函數的返回值
- });
- this.onRejecteds.push(() => {
- let x = onRejected(this.reason);
- resolvePromise(promise, x, resolve, reject); // 處理回調函數的返回值
- });
- }
- }
三、實現resolvePromise
① 如果回調函數返回值與then()方法中創建的Promise對象相同則拋出錯誤,這相當于是自己等自己會進入死循環。
- let p1 = new Promise((resolve, reject) => {
- resolve(1);
- })
- let p2 = p1.then((value) => { // p2即then方法內創建Promise對象
- return p2;
- });
- // 結果拋出錯誤,顯示Chaining cycle detected for promise #<Promise>
- const resolvePromise = function(promise, x, resolve, reject) {
- if (promise === x) { // 禁止resolve自己
- throw new Error("Chaining cycle detected for promise #<Promise>");
- }
- }
② 如果回調函數返回的是一個Promise對象或者帶then方法的類Promise對象,又或者一個函數,因為函數上也可能有then方法,那么我們需要取出then方法并執行,對于Promise對象而言,then方法的執行就會注冊相應的回調函數,等Promise狀態變為最終狀態后就會執行對應的回調函數,回調函數執行后就可以拿到Promise對象的value值,然后將該value值作為調用then方法創建的Promise的對象的value值。
- const resolvePromise = function(promise, x, resolve, reject) {
- ......
- if ((x && typeof x === "object") || typeof x === "function") { // 如果是對象或者函數,函數也可能有then方法
- let executed;
- try {
- let then = x.then; // 嘗試取出then方法
- if (typeof then === "function") { // 如果該對象上存在then方法,那么是個Promise對象或者包含then方法的對象
- then.call(x, function (y) { // 執行then方法,對于真正的Promise對象,則會注冊回調,等到狀態變化后,回調函數會執行,回調中能接收到Promise的value值
- if (executed) return;
- executed = true; // 注冊的回調函數只能執行一次
- resolvePromise(promise, y, resolve, reject); // 返回值還可能是一個Promise對象,故需要遞歸直到變為普通值為止
- }, function (e) {
- if (executed) return;
- executed = true;
- reject(e);
- });
- } else { // 不包含then方法的普通對象,直接resolve即可
- resolve(x);
- }
- } catch(e) {
- if (executed) return;
- executed = true;
- reject(e);
- }
- } else {
- resolve(x);
- }
- }
四、實現catch
catch可以看做是一個特殊的then方法,其內部會調用then()方法,但是僅注冊拒絕的回調函數,這也就是then(onFulfilled, onRejected)和then(onFulfilled).catch(onRejected)的區別,如果將onRejected寫到then中,那么當then的onFulfilled發生錯誤的時候,onRejected就無法捕獲到其中的錯誤,而寫到catch中,那么就相當于是下一個then()方法,故能捕獲到上一個then()方法中發生的錯誤。
- class Promise {
- catch(onRejected) {
- return this.then(null, onRejected); // 僅注冊拒絕的回調函數
- }
- }
五、總結
Promise其實就是一個類,內部有state、value、reason等屬性,分別用于存儲當前Promise的狀態、執行成功后的返回值,執行失敗的原因,同時內部還提供了resolve、reject兩個方法,這兩個方法會以參數的形式傳遞給執行器,即傳遞到外部,以便修改Promise的狀態。
Promise還提供了一個then方法用于注冊回調函數,注冊回調的時候與當前Promise的狀態有關,如果是最終狀態,則立即執行,如果是等待狀態,則先保存起來,等到調用resolve或reject方法的時候再取出回調并執行。注冊的回調函數可能會返回各種各樣的值:
如果返回的是普通值,那么直接用then返回的Promise的resolve方法resolve即可;
如果返回的是Promise對象或者是帶then方法的對象或函數,那么需要調用其then方法并注冊一個自定義回調用于接收當前Promise的值,等該Promise變為最終狀態后會執行回調就可以拿到其value,最后將其作為then返回的Promise的value,即resolve(x)。完整源碼如下:
- var PENDING = "pending"; // 等待狀態
- var FULFILLED = "fulfilled"; // 執行、完成狀態
- var REJECTED = "rejected"; // 拒絕、未完成狀態
- // 傳入then()方法中創建的Promise對象,回調函數的返回值x,then()方法中創建的Promise的resolve、reject
- const resolvePromise = function(promise, x, resolve, reject) {
- if (promise === x) { // 禁止resolve自己
- throw new Error("Chaining cycle detected for promise #<Promise>");
- }
- if ((x && typeof x === "object") || typeof x === "function") { // 如果是對象或者函數,函數也可能有then方法
- let executed;
- try {
- let then = x.then; // 嘗試取出then方法
- if (typeof then === "function") { // 如果該對象上存在then方法,那么是個Promise對象或者包含then方法的對象
- then.call(x, function (y) { // 執行then方法,對于真正的Promise對象,則會注冊回調,等到狀態變化后,回調函數會執行,回調中能接收到Promise的value值
- if (executed) return;
- executed = true; // 注冊的回調函數只能執行一次
- resolvePromise(promise, y, resolve, reject); // 返回值還可能是一個Promise對象,故需要遞歸直到變為普通值為止
- }, function (e) {
- if (executed) return;
- executed = true;
- reject(e);
- });
- } else { // 不包含then方法的普通對象,直接resolve即可
- resolve(x);
- }
- } catch(e) {
- if (executed) return;
- executed = true;
- reject(e);
- }
- } else {
- resolve(x);
- }
- }
- class Promise {
- constructor(executor) { // 傳入執行器函數
- this.state = PENDING; // Promise對象創建完成后默認為等待狀態
- this.value = undefined; // 保存異步操作的結果
- this.reason = undefined; // 保存失敗的原因
- this.onFulfilleds = []; // 保存then中注冊的完成回調函數
- this.onRejecteds = []; // 保存then中注冊的拒絕回調函數
- const resolve = (value) => {
- if (this.state === PENDING) { // 防止用戶多次resolve,以第一次resolve為準
- this.value = value;
- this.state = FULFILLED; // 將Promise對象的狀態改為完成狀態
- this.onFulfilleds.forEach(fn => fn()); // 取出then中注冊的完成回調函數并執行
- }
- };
- const reject = (reason) => {
- if (this.state === PENDING) { // 防止用戶多次reject
- this.reason = reason;
- this.state = REJECTED; // 將Promise對象的狀態改為未完成狀態
- this.onRejecteds.forEach(fn => fn()); // 取出then中注冊的拒絕回調函數并執行
- }
- };
- try {
- executor(resolve, reject); // 執行器由用戶傳入可能會發生錯誤,所以需要進行捕獲
- } catch(e) {
- reject(e);
- }
- }
- then(onFulfilled, onRejected) {
- onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (value) => { // 如果onFulfilled不是函數,則初始化一個完成處理函數
- return value;
- };
- onRejected = typeof onRejected === "function" ? onRejected : (reason) => { // 如果onRejected不是函數,則初始化一個未完成處理函數
- throw reason; // 傳什么就拋出什么
- }
- let promise;
- switch(this.state) {
- case FULFILLED: // 調用then方法的時候,當前Promise狀態已經變成完成狀態,則可用立即執行完成的回調函數
- promise = new Promise((resolve, reject) => {
- setTimeout(() => {
- try {
- let x = onFulfilled(this.value);
- resolvePromise(promise, x, resolve, reject);
- } catch(e) {
- console.log(e);
- reject(e);
- }
- });
- });
- break;
- case REJECTED:
- promise = new Promise((resolve, reject) => {
- setTimeout(() => {
- try {
- let x = onRejected(this.reason);
- resolvePromise(promise, x, resolve, reject);
- } catch(e) {
- reject(e);
- }
- });
- });
- break;
- case PENDING:
- promise = new Promise((resolve, reject) => {
- this.onFulfilleds.push(() => {
- try {
- let x = onFulfilled(this.value);
- resolvePromise(promise, x, resolve, reject);
- } catch(e) {
- reject(e);
- }
- });
- this.onRejecteds.push(() => {
- try {
- let x = onRejected(this.reason);
- resolvePromise(promise, x, resolve, reject);
- } catch(e) {
- reject(e);
- }
- });
- });
- break;
- }
- return promise;
- }
- catch(onRejected) {
- return this.then(null, onRejected); // 僅注冊拒絕的回調函數
- }
- }