本篇文章將會帶你們從分解promise入手,一步步實現一個promise。但閱讀以前須要比較熟練地瞭解瞭解用法,結合用法看文章可能更容易理解。前端
先看一下簡單的用法。面試
const promise = new Promise((resolve, reject) => { setTimeout(() => { resolve('success') }) }) .then(value => { ... }, reason => { ... }) .catch(error => { ... })
Promise的構造函數接收了一個回調,這個回調就是下面要講到的執行器,執行器的參數resolve, reject也是兩個函數,
負責改變promise實例的狀態和它的值,then函數中的回調在狀態改變後執行。數組
注意:不是then函數在狀態改變後執行,而是then中的回調函數在狀態改變後執行。then方法會將其中的回調放入執行隊列,promise的狀態改變後再將隊列中的函數一一執行promise
若是要實現一個最簡單的promise類,內部結構都要包含什麼呢?併發
行隊列的操做,後文會講到異步
const PENDING = 'pending' const FULFILLED = 'fulfiled' const REJECTED = 'rejected' class NewPromise { constructor(handler) { this.state = PENDING this.value = undefined this.successCallback = [] this.failureCallback = [] try { handler(this.resolve.bind(this), this.reject.bind(this)) } catch (e) { this.reject(e) } } // resolve和reject方法 resolve(value) { ... } reject(reason) { ... } // then方法 then(onFulfilled, onRejected) { ... } }
結構中的每一個部分是如何實現的呢?函數
執行器是咱們初始化promise時候傳入的回調,是咱們操做promise的入口,因此執行器的實現不復雜,也就是將咱們傳入的回調執行一下。this
class NewPromise { ... handler(resolve.bind(this), reject.bind(this)) ... }
實際上,執行器會接受兩個回調,resolve和reject。它們真正起到改變promise狀態的做用。spa
其實是兩個函數,所作的事情不復雜。prototype
const PENDING = 'pending' const FULFILLED = 'fulfiled' const REJECTED = 'rejected' class NewPromise { constructor(handler) { this.state = PENDING this.value = undefined // 兩個隊列,後面會講到 this.successCallback = [] this.failureCallback = [] try { // 執行器,因爲resolve和reject中用到了this,這裏須要bind一下 handler(this.resolve.bind(this), this.reject.bind(this)) } catch (e) { this.reject(e) } } resolve(value) { if (this.state !== PENDING) return this.state = FULFILLED this.value = value // 用setTimeout模擬異步方式 setTimeout(() => { this.successCallback.forEach(item => { item(value) }) }) } reject(reason) { if (this.state !== PENDING) return this.state = REJECTED this.value = reason setTimeout(() => { this.failureCallback.forEach(item => { setTimeout(item(reason)) }) }) } }
看一下它們的實現,改變狀態、賦值value,最重要的一點:循環執行then方法註冊到隊列中的回調。
而規範中要求回調以異步方式執行,保證在執行全部的回調以前,全部回調已經經過then註冊完成,因此這裏用setTimeout模擬了一下。
(翻譯整理自Promise/A+ 規範)
promise必須提供then方法來訪問這個promise當前或者最終的值
then方法有兩個參數:onFulfilled, onRejected,都是可選的。關於這兩個參數,這裏有幾個規則:
實際上忽略的意思也就是若是不是函數,默認給它賦值成函數,返回值爲then所屬的promise的值。這樣是作是爲了在then()函數未傳回調的時候,能夠將promise的值傳遞下去。場景以下:
promise(resolve => resolve('success')) .then() .then(function(value) { console.log(value) })
具體實現上,在它不是函數的時候能夠給它賦值一個默認函數,也能夠直接調用新返回的promise中的resolve或reject將值傳下去,來達到忽略的效果
就像下邊這樣:
const promise = new Promise((resolve, reject) => { setTimeout(() => resolve('success')) }) promise.then(res => { console.log(res, '第一次'); }) promise.then(res => { console.log(res, '第二次'); })
鑑於這種狀況,須要在咱們實現的promise內部維護兩個隊列,隊列中的元素是then方法內註冊的回調函數(onFulfilled, onRejected),每調用一次then,就向隊列中註冊一個回調,它們會在promise狀態改變時被依次執行。
promise2 = promise1.then(onFulfilled, onRejected);
then返回的promise(也就是promise2)的狀態,取決於其回調函數(onFulfilled或onRejected)的返回值或者promise1的狀態,具體表現爲:
上面咱們認識了then方法,結合定義和平時的用法能夠猜想出咱們本身實現的promise內的then方法須要作下邊幾件事:
promise狀態被改變的時候執行。保證了規範中的onFulfilled, onRejected的執行時機。
then所屬的Promise狀態不爲pending時,執行隊列中的回調開始依次執行,而後根據已經改變的狀態以及回調的返回值來決定新的promise的狀態
const promise1 = new Promise((resolve, reject) =>{ ... }) const promise2 = promise1.then(value => { return 'success' }, reason => { return 'failed' })
假設promise1被resolve了,因爲then中傳入了表明onFulfilled的回調而且返回值爲success,那麼promise2會被resolve,值爲success。 假設promise2被reject了,因爲then中傳入了表明onRejected的回調而且返回值爲failed,那麼promise2會被reject,reason是failed
下面一步步來實現then方法,先上結構:
class NewPromise { constructor(handler) { this.state = PENDING this.value = undefined // 兩個隊列,存放onFulfiled 和 onRejected this.successCallback = [] this.failureCallback = [] try { handler(this.resolve.bind(this), this.reject.bind(this)) } catch (e) { this.reject(e) } } then(onFulfilled, onRejected) { return new NewPromise((resolveNext, rejectNext) => { // pengding狀態向隊列中註冊回調 if (state === PENDING) { successCallback.push(onFulfilled) failureCallback.push(onRejected) } // 要保證在當前promise狀態改變以後,再去經過resolveNext或者rejectNext改變新的promise的狀態 if (state === FULFILLED) { resolveNext(value) } if (state === REJECTED) { rejectNext(value) } }) } }
上面的結構基本實現了then函數的大概邏輯,可是沒有實現根據onFulfilled, onRejected兩個回調的執行結果來決定新的promise的狀態的效果,
只是將他們分別放到了各自的執行隊列中去。
最終then返回的promise的狀態和onFulfilled, onRejected的執行結果有關。我根據規範和實際狀況整理了一張圖:
而後讓咱們用代碼來實現它(單以onFulfilled的執行狀況舉例)
try { // 正常狀況 if (typeof onFulfilled !== 'function') { // 不是函數,直接忽略,將then所屬的promise做爲then返回的promise的值resolve來作到值的傳遞 resolveNext(value) } else { // 獲取then函數回調的執行結果 const res = onFulfilled(value) if (res instanceof NewPromise) { // 當執行結果返回的是一個promise實例,等待這個promise狀態改變後再改變then返回的promise的狀態 res.then(resolveNext, rejectNext) } else { // 當返回值是普通值,將其做爲新promise的值resolve resolveNext(res) } } } catch (e) { // 出現異常,新promise的狀態變爲rejected,reason就是錯誤對象 rejectNext(e) }
整個這一部分,須要放入隊列中等待then所屬的promise狀態改變再執行,從而改變then返回的promise的狀態。
因此,咱們須要將這一塊包裝起來。整合起來就是:
class NewPromise { constructor(handler) { this.state = PENDING this.value = undefined // 兩個隊列,存放onFulfiled 和 onRejected this.successCallback = [] this.failureCallback = [] try { handler(this.resolve.bind(this), this.reject.bind(this)) } catch (e) { this.reject(e) } } then(onFulfilled, onRejected) { const { state, value } = this return new NewPromise((resolveNext, rejectNext) => { const resolveNewPromise = value => { try { // 正常狀況 if (typeof onFulfilled !== 'function') { // 不是函數,直接忽略,將then所屬的promise做爲then返回的promise的值resolve來作到值的傳遞 resolveNext(value) } else { // 獲取then函數回調的執行結果 const res = onFulfilled(value) if (res instanceof NewPromise) { // 當執行結果返回的是一個promise實例,等待這個promise狀態改變後再改變then返回的promise的狀態 res.then(resolveNext, rejectNext) } else { // 當返回值是普通值,將其做爲新promise的值resolve resolveNext(res) } } } catch (e) { // 出現異常,新promise的狀態變爲rejected,reason就是錯誤對象 rejectNext(e) } } const rejectNewPromise = reason => { try { // 正常狀況 if (typeof onRejected !== 'function') { // 不是函數,直接忽略,將then所屬的promise做爲then返回的promise的值reject來作到值的傳遞 rejectNext(reason) } else { // 獲取then函數回調的執行結果 const res = onRejected(reason) if (res instanceof NewPromise) { // 當執行結果返回的是一個promise實例,等待這個promise狀態改變後再改變then返回的promise的狀態 res.then(resolveNext, rejectNext) } else { // 當返回值是普通值,將其做爲新promise的值reject rejectNext(res) } } } catch (e) { // 出現異常,新promise的狀態變爲rejected,reason就是錯誤對象 rejectNext(e) } } if (state === PENDING) { this.successCallback.push(resolveNewPromise) this.failureCallback.push(rejectNewPromise) } // 要保證在當前promise狀態改變以後,再去改變新的promise的狀態 if (state === FULFILLED) { resolveNewPromise(value) } if (state === REJECTED) { rejectNewPromise(value) } }) } }
咱們在本身的實現中是定義了兩個數組做爲任務隊列存放then註冊的回調,而實際的promise中,
then方法是將回調註冊到微任務隊列中。等到promise狀態改變,執行微任務隊列中的任務。微任務在概念上能夠認爲是異步任務,這也印證了規範中then的回調必須
異步執行的說法。關於事件循環的一些知識點,我總結過一篇文章,今天,我明白了JS事件循環機制
catch函數是用來處理異常的,當promise狀態變爲rejected的時候,捕獲到錯誤緣由。那麼假設不用catch,也能夠在then函數的第二個回調中捕獲這個錯誤。並且catch返回的是一個promise,因此與調用Promise.prototype.then(undefined, onRejected)的行爲是同樣的
catch(onRejected) { return this.then(undefined, onRejected) }
Promise.resolve(value)返回的是一個promise對象,用於將傳入的值value包裝爲promise對象。
那這樣作有什麼意義呢?實際上value多是一個不肯定的值,多是promise也可能不是,沒準能夠調用then方法,也沒準不能夠。可是能夠經過resolve方法將行爲統一塊兒來。
const promise = function() { if (shouldBePromise) { return new Promise(function(resolve, reject) { resolve('ok') }) } return 'ok' } promise().then(() => { ... })
promise返回的結果取決於shouldBePromise,假設shouldBePromise爲false,那麼promise就返回了字符串ok,下邊就不能調用then方法。這個時候能夠用Promise().resolve包起來,這樣promise返回的始終是一個promise實例,保證了then方法的順利調用。
Promise.resolve(promise()).then(() => { ... })
總結一下特色:Promise.resolve的參數若是:
static resolve(value) { // value不存在,直接返回一個resolved狀態的promise if (!value) { return new NewPromise(function (resolve) { resolve() }) } // value是promise實例,直接返回 // 在這裏須要首先判斷是不是promise實例,再進行下邊的判斷 // 由於咱們本身構造的promise也是是object,也有then方法 if (value instanceof NewPromise) { return value } // 是thenable對象,返回的新的promise實例須要在value狀態改變後再改變,且狀態跟隨value的狀態 if (typeof value === 'object' && typeof value.then === 'function') { return new NewPromise((resolve, reject) => { value.then(resolve, reject) }) } // value是普通值,返回新的promise並resolve這個普通值 return new NewPromise(resolve => { resolve(value) }) }
reject方法對比resolve相對簡單,它老是返回一個reject的promise對象,reject的緣由是咱們傳入的reason
static reject(reason) { return new NewPromise((resolve, reject) => { reject(reason) }) }
返回的是一個promise,做用是在promise結束時,不管結果是fulfilled或者是rejected,都會執行回調函數。返回的新promise的狀態和值取決於原來的promise。
finally(callback) { // 返回值是promise對象,回調在then中執行,也就符合了promise結束後調用的原則 return this.then( // then方法的onFulfiled 和 onRejected都會被傳入,保證不管resolved或rejected都會被執行 // 獲取到promise執行成功的結果,將這個結果做爲返回的新promise的值 res => NewPromise.resolve(callback()) .then(() => { return res }), // 獲取執行失敗的結果。原理同上 error => NewPromise.resolve(callback()) .then(() => { throw error }) ) }
Promise.all(param) 接收一個參數數組,返回一個新的promise實例。當參數數組內的promise都resolve後或者參數內的實例執行完畢後,新返回的promise纔會resolve。
數組內任何一個promise失敗(rejected),新返回的promise失敗,失敗緣由就是第一個失敗的promise的結果。
const p1 = Promise.resolve(1), coint p2 = Promise.resolve(2), const p3 = Promise.resolve(3); Promise.all([p1, p2, p3]).then(function (results) { console.log(results); // [1, 2, 3] });
由此可知,all方法須要返回一個新的promise實例,而後根據接收的參數數組執行狀況,控制新的promise實例的狀態與值
static all(instanceList) { return new NewPromise((resolve, reject) => { // 定義存放結果的數組 const results = [] let count = 0 if (instanceList.length === 0) { resolve(results) return } instanceList.forEach((item, index) => { // 因爲實例列表中的每一個元素多是各類各樣的,因此要用this.resolve方法包裝一層 this.resolve(item).then(res => { results[index] = res count++ // 當都執行完,resolve新返回的promise if (count === instanceList.length) { resolve(results) } }, error => { // 一旦有一個出錯,就reject新返回的promise reject(error) }) }) }) }
實現以後能夠清楚的看到,all方法會並行執行全部promise,結果按傳入的promise數組的順序輸出。這讓我想起了之前面試碰到的一個題目:
併發全部請求,按順序輸出。能夠用Promise.all實現。但實際上會有請求失敗的狀況,因此更好的方式是下邊要講到的Promise.allSettled。
若是說finally提供了在單個promise是否成功都須要執行代碼提供了一種方式,那麼allSettled就是爲多個promise是否成功的場景提供了一樣的操做方式。
Promise.allSettled()方法返回一個promise,該promise在全部給定的promise已被解析或被拒絕後解析,而且每一個對象都描述每一個promise的結果。
const promise1 = Promise.resolve(3); const promise2 = new Promise((resolve, reject) => setTimeout(reject, 100, 'foo')); const promises = [promise1, promise2]; Promise.allSettled(promises). then((results) => results.forEach((result) => console.log(result.status))); // expected output: // "fulfilled" // "rejected"
不一樣於Promise.all的一旦有一個執行失敗,就沒法得到全部promise都執行完成的時間點的特色。不管某個promise成功與否,一旦全部的promise都完成,就能夠得到這個時間點。由於其返回的新的promise,老是被resolve的,而且值是全部promise執行結果的描述。
[ {"status":"rejected","reason":"失敗"}, {"status":"fulfiled","value":"成功"} ]
要實現它,須要在每一個promise執行的時候把結果記錄下來放進一個數組內,最後在全部promise執行完成後,resolve結果數組,改變返回的新的promise的狀態。
static allSettled(instanceList) { return new NewPromise((resolve, reject) => { const results = [] let count = 0 if (instanceList.length === 0) { resolve([]) return } // 定義一個函數,來生成結果數組 const generateResult = (result, i) => { count++ results[i] = result // 一旦所有執行完成,resolve新返回的promise if (count === instanceList.length) { resolve(results) } } instanceList.forEach((item, index) => { // 在每一個promise完成後將狀態記錄到結果數組中 this.resolve(item).then( value => { generateResult({ status: FULFILLED, value }, index) }, reason => { generateResult({ status: REJECTED, reason }, index) } ) }) }) }
與all方法相似,接受一個實例數組爲參數,返回新的promise。但區別是一旦實例數組中的某個promise解決或拒絕,返回的promise就會解決或拒絕。
static race(instanceList) { return new NewPromise((resolve, reject) => { if (instanceList.length === 0) { resolve([]) return } instanceList.forEach(item => { // 因爲實例列表中的每一個元素多是各類各樣的,因此要用this.resolve方法包裝一層 this.resolve(item).then(res => { // 一旦有一個resolve了,那麼新返回的promise狀態就被resolve resolve(res) }, error => { reject(error) }) }) }) }
到此爲止就實現了一個相對完整的promise,代碼以下:
class NewPromise { constructor(handler) { this.state = PENDING this.value = undefined this.successCallback = [] this.failureCallback = [] try { handler(this.resolve.bind(this), this.reject.bind(this)) } catch (e) { // 執行器出現錯誤須要reject this.reject(e) } } resolve(value) { if (this.state !== PENDING) return this.state = FULFILLED this.value = value // 規範中要求then中註冊的回調以異步方式執行,保證在resolve執行全部的回調以前, // 全部回調已經經過then註冊完成 setTimeout(() => { this.successCallback.forEach(item => { item(value) }) }) } reject(reason) { if (this.state !== PENDING) return this.state = REJECTED this.value = reason setTimeout(() => { this.failureCallback.forEach(item => { item(reason) }) }) } then(onFulfilled, onRejected) { const { state, value } = this return new NewPromise((resolveNext, rejectNext) => { const resolveNewPromise = value => { try { // 正常狀況 if (typeof onFulfilled !== 'function') { // 不是函數,直接忽略,將then所屬的promise做爲then返回的promise的值resolve來作到值的傳遞 resolveNext(value) } else { // 獲取then函數回調的執行結果 const res = onFulfilled(value) if (res instanceof NewPromise) { // 當執行結果返回的是一個promise實例,等待這個promise狀態改變後再改變then返回的promise的狀態 res.then(resolveNext, rejectNext) } else { // 當返回值是普通值,將其做爲新promise的值resolve resolveNext(res) } } } catch (e) { // 出現異常,新promise的狀態變爲rejected,reason就是錯誤對象 rejectNext(e) } } const rejectNewPromise = reason => { try { // 正常狀況 if (typeof onRejected !== 'function') { // 不是函數,直接忽略,將then所屬的promise做爲then返回的promise的值reject來作到值的傳遞 rejectNext(reason) } else { // 獲取then函數回調的執行結果 const res = onRejected(reason) if (res instanceof NewPromise) { // 當執行結果返回的是一個promise實例,等待這個promise狀態改變後再改變then返回的promise的狀態 res.then(resolveNext, rejectNext) } else { // 當返回值是普通值,將其做爲新promise的值reject rejectNext(res) } } } catch (e) { // 出現異常,新promise的狀態變爲rejected,reason就是錯誤對象 rejectNext(e) } } if (state === PENDING) { this.successCallback.push(resolveNewPromise) this.failureCallback.push(rejectNewPromise) } // 要保證在當前promise狀態改變以後,再去改變新的promise的狀態 if (state === FULFILLED) { resolveNewPromise(value) } if (state === REJECTED) { rejectNewPromise(value) } }) } catch(onRejected) { return this.then(undefined, onRejected) } finally(callback) { // 返回值是promise對象,回調在then中執行,也就符合了promise結束後調用的原則 return this.then( // then方法的onFulfiled 和 onRejected都會被傳入,保證不管resolved或rejected都會被執行 // 獲取到promise執行成功的結果,將這個結果做爲finally返回的新的promise的值 res => NewPromise.resolve(callback()) .then(() => { return res }), // 獲取執行失敗的結果。原理同上 error => NewPromise.resolve(callback()) .then(() => { throw error }) ) } static allSettled(instanceList) { return new NewPromise((resolve, reject) => { const results = [] let count = 0 if (instanceList.length === 0) { resolve([]) return } // 定義一個函數,來生成結果數組 const generateResult = (result, i) => { count++ results[i] = result // 一旦所有執行完成,resolve新返回的promise if (count === instanceList.length) { resolve(results) } } instanceList.forEach((item, index) => { // 在每一個promise完成後將狀態記錄到結果數組中 this.resolve(item).then( value => { generateResult({ status: FULFILLED, value }, index) }, reason => { generateResult({ status: REJECTED, reason }, index) } ) }) }) } static resolve(value) { // value不存在,直接返回一個resolved狀態的promise if (!value) { return new NewPromise(function (resolve) { resolve() }) } // value是promise實例,直接返回 // 在這裏須要首先判斷是不是promise實例,再進行下邊的判斷 // 由於咱們本身構造的promise也是是object,也有then方法 if (value instanceof NewPromise) { return value } // 是thenable對象,返回的新的promise實例須要在value狀態改變後再改變,且狀態跟隨value的狀態 if (typeof value === 'object' && typeof value.then === 'function') { return new NewPromise((resolve, reject) => { value.then(resolve, reject) }) } // value是普通值,返回新的promise並resolve這個普通值 return new NewPromise(resolve => { resolve(value) }) } static reject(reason) { return new NewPromise((resolve, reject) => { reject(reason) }) } static all(instanceList) { return new NewPromise((resolve, reject) => { // 定義存放結果的數組 const results = [] let count = 0 if (instanceList.length === 0) { resolve(results) return } instanceList.forEach((item, index) => { // 因爲實例列表中的每一個元素多是各類各樣的,因此要用this.resolve方法包裝一層 this.resolve(item).then(res => { results[index] = res count++ // 當都執行完,resolve新返回的promise if (count === instanceList.length) { resolve(results) } }, error => { // 一旦有一個出錯,就reject新返回的promise reject(error) }) }) }) } static race(instanceList) { return new NewPromise((resolve, reject) => { if (instanceList.length === 0) { resolve([]) return } instanceList.forEach(item => { // 因爲實例列表中的每一個元素多是各類各樣的,因此要用this.resolve方法包裝一層 this.resolve(item).then(res => { // 一旦有一個resolve了,那麼新返回的promise狀態就被resolve resolve(res) }, error => { reject(error) }) }) }) } }
想看我寫的更多技術文章能夠關注公衆號:一口一個前端