亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

首頁 慕課教程 ES6-10 入門教程 ES6-10 入門教程 ES6+ 實現一個簡版的 Promise

ES6+ 實現一個簡版的 Promise

1. 前言

上一節我們學習了 ES6 Promise的基本用法,并且我們知道 Promise 最早出現在社區,所以ES6 中 Promise 也是遵循一個標準的規范的。這個規范就是 Promise A+ 規范 也就是任何人都可以遵循這個規范實現一個自己的 Promise,由于每個人實現的方式有所差異,Promise A+ 規范 給出了一些要求和兼容方式。

本節我們將根據 Promise A+ 規范 實現一個簡版的 Promise API。

2. 實現步驟

上一節我們已經知道了 Promise 是一個類,默認接收一個參數 executor(執行器),并且會立即執行。所以首先需要創建一個 Promise 的類,然后傳入一個回調函數并執行它,故有如下的初始代碼:

class Promise {
  constructor(executor) {
    executor();
  }
}

Promise 有三個狀態:等待(padding)、成功(fulfilled),失?。╮ejected)。默認是等待狀態,等待態可以突變為成功態或失敗態,所以我們可以定義三個常量來存放這三個狀態

const PENDING = 'PENDING';
const RESOLVED = 'RESOLVED';	// 成功態
const REJECTED = 'REJECTED';	// 失敗態
class Promise {
  constructor(executor) {
    this.status = PENDING;	// 默認是等待態
    executor();
  }
}

這樣我們就知道了 Promise 的基本狀態,那內部的狀態是怎么突變為成功或失敗的呢?這里執行器(executor)會提供兩個個方法用于改變 Promise 的狀態,所以我們需要在初始化時定義 resolve 和 reject 方法:在成功的時候會傳入成功的值,在失敗的時候會傳入失敗的原因。并且每個Promise 都會提供 then方法用于鏈式調用。

class Promise {
  constructor(executor) {
    this.status = PENDING;
    const resolve = (value) => {};
    const reject = (reason) => {};
    // 執行executor時,傳入成功或失敗的回調
    executor(resolve, reject);
  }
  then(onfulfilled, onrejected) {
    
  }
}

這時我們就可以開始著手去更改 Promise的狀態了,由于默認情況下 Promise 的狀態只能從 pending 到 fulfilled 和 rejected 的轉化。

class Promise {
  constructor(executor) {
    this.status = PENDING;
    const resolve = (value) => {
      // 只有等待態時才能更改狀態
      if (this.status === PENDING) {
        this.status = RESOLVED;
      }
    };
    const reject = (reason) => {
      if (this.status === PENDING) {
        this.status = REJECTED;
      }
    };
    executor(resolve, reject);
  }
  ...
}

成功和失敗都會返回對應的結果,所以我們需要定義成功的值和失敗的原因兩個全局變量,用于存放返回的結果。

class Promise {
  constructor(executor) {
    this.status = PENDING;
    this.value = undefined;
    this.reason = undefined;
    const resolve = (value) => {
      // 只有等待態時才能更改狀態
      if (this.status === PENDING) {
        this.value = value;
        this.status = RESOLVED;
      }
    };
    const reject = (reason) => {
      if (this.status === PENDING) {
        this.reason = reason;
        this.status = REJECTED;
      }
    };
    executor(resolve, reject);
  }
  ...
}

這時我們就已經為執行器提供了兩個回調函數了,如果在執行器執行時拋出異常時,我們需要使用 try…catch 來補貨一下。由于是拋出異常,所以,需要調用 reject 方法來修改為失敗的狀態。

try {
  executor(resolve, reject);
} catch(e) {
  reject(e)
}

我們知道實例在調用 then 方法時會傳入兩個回調函數 onfulfilled, onrejected 去執行成功或失敗的回調,所以根據狀態會調用對應的函數來處理。

then(onfulfilled, onrejected) {
  if (this.status === RESOLVED) {
    onfulfilled(this.value)
  }
  if (this.status === REJECTED) {
    onrejected(this.reason)
  }
}

這樣我們就完了 Promise 最基本的同步功能,

let promise = new Promise((resolve, reject) => {
  resolve('value');
  // throw new Error('錯誤');
  // reject('error reason')
  // setTimeout(() => {
  //   resolve('value');
  // }, 1000)
})
promise.then((data) => {
  console.log('resolve response', data);
}, (err) => {
  console.log('reject response', err);
})

用上面的代碼對我們寫的 Promise 進行驗證,通過測試用例可知,我們寫的 Promise 只能在同步中運行,當我們使用 setTimeout 異步去返回時,并沒有預想的在then的成功回調中打印結果。

對于這種異步行為需要專門處理,如何處理異步的內容呢?我們知道在執行異步任務時 Promise 的狀態并沒有被改變,也就是并沒有執行 resolve 或 reject 方法,但是 then 中的回調已經執行了,這時就需要增加當 Promise 還是等待態的邏輯,在等待態時把回調函數都存放起來,等到執行 resolve 或 reject 再依次執行之前存放的then的回調函數,也就是我們平時用到的發布訂閱模式。實現步驟:

  • 首先,需要在初始化中增加存放成功的回調函數和存放失敗的回調函數;
  • 然后,由于是異步執行 resolve 或 reject 所以需要在 then 方法中把回調函數存放起來;
  • 最后,當執行 resolve 或 reject 時取出存放的回調函數依次執行。

根據以上的實現步驟可以得到如下的代碼:

class Promise {
  constructor(executor) {
	this.status = PENDING;
    this.value = undefined; // 成功的值
    this.reason = undefined; // 失敗的原因
+   // 存放成功的回調函數
+    this.onResolvedCallbacks = [];
+    // 存放失敗的回調函數
+    this.onRejectedCallbacks = [];
    let resolve = (value) => {
      if (this.status === PENDING) {
		this.value = value;
        this.status = RESOLVED;
+       // 異步時,存放在成功的回調函數依次執行
+       this.onResolvedCallbacks.forEach(fn => fn())
      }
    };
    let reject = (reason) => {
      if (this.status === PENDING) {
		this.value = reason;
        this.status = REJECTED;
+       // 異步時,存放在失敗的回調函數依次執行
+       this.onRejectedCallbacks.forEach(fn => fn())
      }
    };
    try {
      executor(resolve, reject);
    } catch(e) {
      reject(e)
    }
  }
  then(onfulfilled, onrejected) {
    if (this.status === RESOLVED) {
      onfulfilled(this.value)
    }
    if (this.status === REJECTED) {
      onrejected(this.reason)
    }
+    if (this.status === PENDING) {
+      this.onResolvedCallbacks.push(() => {
+        // TODO
+        onfulfilled(this.value);
+      })
+      this.onRejectedCallbacks.push(() => {
+        // TODO
+        onrejected(this.reason);
+      })
+    }
  }
}

上面的代碼中,在存放回調函數時把 onfulfilled, onrejected 存放在一個函數中執行,這樣的好處是可以在前面增加處理問題的邏輯。這樣我們就完成了處理異步的 Promise 邏輯。下面是測試用例,可以正常的執行 then 的成功回調函數。

let promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('100');
  }, 1000)
})
promise.then((data) => {
  console.log('resolve response:', data); // resolve response: 100
}, (err) => {
  console.log('reject response:', err);
})

到這里我們是不是已經基本實現了 Promise 的功能呢?ES6 中的 then 方法支持鏈式調用,那我們寫的可以嗎?我們在看下面的一個測試用例:

let promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('100');
  }, 1000)
})
promise.then((data) => {
  console.log('resolve response:', data); // resolve response: 100
  return 200
}, (err) => {
  console.log('reject response:', err);
}).then((data) => {
  console.log('data2:', data)
}, null)
// TypeError: Cannot read property 'then' of undefined

然而當我們在執行的時候會報錯,then 是 undefined。為什么會這樣呢?那我們要知道如何滿足鏈式調用的規范,那就是在完成任務后再返回一個Promise 實例。那如何返回一個 Promise 實例呢?在 Promise A+ 規范的 2.2.7 小節在有詳細的描述,再實例化一個 promise2 來存放執行后的結果,并返回 promise2。那么我們就要改造 then 方法了。

class Promise {
  ...
  then(onfulfilled, onrejected) {
	let promise2 = new Promise((resolve, reject) => {
      if (this.status === RESOLVED) {
        const x = onfulfilled(this.value)
		resolve(x)
      }
      if (this.status === REJECTED) {
        const x = onrejected(this.reason);
        reject(x)
      }
      if (this.status === PENDING) {
        this.onResolvedCallbacks.push(() => {
	        const x = onfulfilled(this.value)
			resolve(x)
        })
        this.onRejectedCallbacks.push(() => {
	        const x = onrejected(this.reason);
        	reject(x)
        })
      }
    })

    return promise2
  }
}

再使用上面的測試用例,就可以得到正確的結果:

let promise = new Promise((resolve, reject) => {
  resolve('100');
})
promise.then((data) => {
  console.log('data1:', data);	// data1: 100
  return 200
}, null).then((data) => {
  console.log('data2:', data);	// data2: 200
  throw new Error('error')
}, null).then(null, () => {
  consol.log('程序報錯...')
})

上面的測試用例中,當 then 的回調函數拋出異常時需要去捕獲錯誤,傳到下一個 then 的失敗回調函數中。

class Promise {
  ...
  then(onfulfilled, onrejected) {
		let promise2 = new Promise((resolve, reject) => {
      if (this.status === RESOLVED) {
        try{
			const x = onfulfilled(this.value)
			resolve(x)
        } catch(e) {
			reject(e)
        }
      }
      if (this.status === REJECTED) {
        try{
			const x = onrejected(this.reason);
        	resolve(x)
        } catch(e) {
			reject(e)
        }
      }
      if (this.status === PENDING) {
        this.onResolvedCallbacks.push(() => {
          try{
            const x = onfulfilled(this.value)
            resolve(x)
          } catch(e) {
            reject(e)
          }
        })
        this.onRejectedCallbacks.push(() => {
          try{
            const x = onrejected(this.reason);
            resolve(x)
          } catch(e) {
            reject(e)
          }
        })
      }
    })
    return promise2
  }
}

到這里為止我們就已經實現了一個簡版的 Promise,因為Promise是一個規范,很多人都可以實現自己的 Promise 所以 Promise A+ 規范做了很多兼容處理的要求,如果想實現一個完整的 Promise 可以參考 Promise A+ 規范 。

3. 小結

本節主要按照 Promise A+ 規范 部分的要求實現了一個簡版的 Promise API 這個 Promise 基本上滿足同步異步的鏈式調用,對基本的異常做了處理。當然 Promise A+ 規范 所規定的細節比較多,剩下的都是對各種異常錯誤的處理,所以后面我們也沒有去實現。另外官網下提供了一個測試用例來驗證我們寫的 Promise 是否符合 Promise A+ 規范 ,所以可以參考 promises-tests 這個庫來完成我們的 Promise 的測試。