Promise對象的特性
要實現Promise
對象首先我們要了解Promise
擁有哪些特性,簡單概括為以下幾點
1、Promise
有三種狀態:pending
(進行中)、fulfilled
(已成功)、rejected
(已失敗)
2、Promise
對象接受一個回調函數作為參數, 該回調函數接受兩個參數,分別是成功時的回調resolve
和失敗時的回調reject
;另外resolve
的參數除了正常值以外, 還可能是一個Promise
對象的實例;reject
的參數通常是一個Error
對象的實例。
3、then
方法返回一個新的Promise
實例,並接收兩個參數onResolved
(fulfilled
狀態的回調);
onRejected
(rejected
狀態的回調,該參數可選)
4、catch
方法返回一個新的Promise
實例
5、finally
方法不管Promise
狀態如何都會執行,該方法的回調函數不接受任何參數
6、Promise.all()
方法將多個多個Promise
實例,包裝成一個新的Promise
實例,該方法接受一個由Promise
對象
組成的數組作為參數(Promise.all()
方法的參數可以不是數組,但必須具有Iterator
接口,且返回的每個成員都是Promise
實例),注意參數中只要有一個實例觸發catch
方法,都會觸發Promise.all()
方法返回的新的實例的catch
方法,如果參數中的某個實例本身調用了catch
方法,將不會觸發Promise.all()
方法返回的新實例的catch
方法
7、Promise.race()
方法的參數與Promise.all
方法一樣,參數中的實例只要有一個率先改變狀態就會將該實例的狀態傳給Promise.race()
方法,並將返回值作為Promise.race()
方法產生的Promise
實例的返回值
8、Promise.resolve()
將現有對象轉為Promise
對象,如果該方法的參數為一個Promise
對象,Promise.resolve()
將不做任何處理;如果參數thenable對象(即具有then方法),Promise.resolve()
將該對象轉為Promise
對象並立即執行then
方法;如果參數是一個原始值,或者是一個不具有then
方法的對象,則Promise.resolve
方法返回一個新的Promise
對象,狀態為fulfilled
,其參數將會作為then
方法中onResolved
回調函數的參數,如果Promise.resolve
方法不帶參數,會直接返回一個fulfilled
狀態的 Promise
對象。需要注意的是,立即resolve()的 Promise 對象,是在本輪“事件循環”(event loop)的結束時執行,而不是在下一輪“事件循環”的開始時。
setTimeout(function () { console.log('three'); }, 0); Promise.resolve().then(function () { console.log('two'); }); console.log('one'); // one // two // three
9、Promise.reject()
同樣返回一個新的Promise
對象,狀態為rejected
,無論傳入任何參數都將作為reject()
的參數
以上就是Promise對象的一些基本特性,下面我們根據Promise
對象的特性,自己來實現一個簡單的Promise對象
第一步、Promise
對象用三種狀態分別是:pending、fulfilled、rejected。
當resolve
的參數為一個Promise
對象的實例的時時候該實例的resolve
的參數即為外層Promise對象then方法中onResolved方法的參數,reject的參數即為外層Promise對象then方法(或catch方法)中onRejected方法的參數
function timeout2() { return new Promise((resolve, reject) => { setTimeout(() => { try { a // 此處a未定義,如果注釋掉這里即正常執行 resolve(200) } catch (e) { reject(e) } }, 1000) }) } function timeout(time) { return new Promise((resolve, reject) => { setTimeout(() => { try { resolve(timeout2()) } catch (e) { reject(e) } }, time) }) } let p = timeout(1000); p.then(res => { console.log(res); // 200 }).catch(err => { console.log(err); // ReferenceError: a is not defined })
由上面的例子我們可以定義一個基本框架:
/* Promise構造函數接收一個executor函數, executor函數執行完同步或異步操作后,調用它的兩個參數resolve和reject 如果操作成功,調用resolve並傳入value 如果操作失敗,調用reject並傳入reason */ class MyPromise { constructor(executor) { if(typeof executor !== 'function') { throw new Error('MyPromise must accept a function as a parameter') } // Promise當前的狀態 this.status = 'pending' // Promise的值 this.data = undefined // Promise resolve時的回調函數集,因為在Promise結束之前有可能有多個回調添加到它上面 this.onResolvedCallback = [] // Promise reject時的回調函數集,因為在Promise結束之前有可能有多個回調添加到它上面 this.onRejectedCallback = [] /* 考慮到執行executor的過程中有可能出錯,所以我們用try/catch塊給包起來, 並且在出錯后以catch到的值reject掉這個Promise,另外因為resolve和reject在外部調用故需要綁定this */ try { executor(this.resolve.bind(this), this.reject.bind(this)) } catch (err) { this.reject(err) } } resolve(value) { // 成功時將狀態改為fulfilled if(this.status === 'padding') { // 如果傳入的值是一個promise實例,則必須等待該Promise對象狀態改變后, // 當前Promsie的狀態才會改變,且狀態取決於參數Promsie對象的狀態 if(value instanceof MyPromise) { value.then(res => { this.data = res this.status = 'fulfilled' //執行resolve的回調函數,將value傳遞到callback中 this.onResolvedCallback.forEach(callback => callback(res)) }, err => { this.data = err this.status = 'rejected' //執行reject的回調函數,將reason傳遞到callback中 this.onRejectedCallback.forEach(callback => callback(err)) }) } else { this.status = 'fulfilled'; this.data = value; //執行resolve的回調函數,將value傳遞到callback中 this.onResolvedCallback.forEach(callback => callback(value)) } } } reject(reason) { // 失敗時將狀態改為rejected if(this.status === 'padding') { this.status = 'rejected' this.data = reason; // 觸發所有的回調函數 this.onRejectedCallback.forEach(item => { item(reason) }) } } }
第二步、實現then方法
Promise
實例的then
方法返回一個新的Promise
實例,並接收兩個參數onResolved
(fulfilled狀態的回調); onRejected
(rejected狀態的回調,該參數可選);
// then方法接收兩個參數,onResolved,onRejected,分別為Promise成功或失敗后的回調 class MyPromise { // .... then(onResolved, onRejected) { // 根據標准,如果then的參數不是function,則我們需要忽略它,此處以如下方式處理 onResolved = typeof onResolved === 'function' ? onResolved : value => {} onRejected = typeof onRejected === 'function' ? onRejected : reason => {} if (this.status === 'fulfilled') { return new MyPromise((resolve, reject) => { }) } if (this.status === 'rejected') { return new MyPromise((resolve, reject) => { }) } if (this.status === 'pending') { return new MyPromise((resolve, reject) => { }) } } }
當我們在鏈式調用Promise
實例的時候,當一個實例的then方法返回另一個實例,第二個then方法指定的回調函數,就會等待這個新的Promise對象狀態發生變化。如果變為fulfilled,就調用第一個回調函數(即onResolved),如果狀態變為rejected,就調用第二個回調函數(即onRejected)。
let p1 = new Promise((resolve,reject) => { setTimeout(function(){ try { resolve(1) } catch (e) { reject(e) } }, 100) }) let p2 = new Promise((resolve,reject) => { setTimeout(function(){ try { resolve(2) } catch (e) { reject(e) } }, 100) }) p1.then(res => { console.log(res); // 1 return p2 }).then(res => { console.log(res); // 2 }).catch(err => { })
第三步、完善then方法
根據上面的例子我們來補充then方法里面的內容
// then方法接收兩個參數,onResolved,onRejected,分別為Promise成功或失敗后的回調 class MyPromise { // .... then(onResolved, onRejected) { // 根據標准,如果then的參數不是function,則我們需要忽略它,此處以如下方式處理 onResolved = typeof onResolved === 'function' ? onResolved : value => value onRejected = typeof onRejected === 'function' ? onRejected : reason => reason // 如果promise1(此處即為this)的狀態已經確定並且是resolved,我們調用onResolved // 因為考慮到有可能throw,所以我們將其包在try/catch塊里 if (this.status === 'fulfilled') { return new MyPromise((resolve, reject) => { try { let result = onResolved(this.data) // 如果onResolved的返回值是一個Promise對象,直接取它的結果做為返回promise實例的結果 if(result instanceof MyPromise) { result.then(resolve, reject) } resolve(result) // 否則,以它的返回值做為返回promise實例的結果 } catch (e) { reject(e) } }) } // rejected狀態的處理方法與上面基本一致 if (this.status === 'rejected') { return new MyPromise((resolve, reject) => { try { let result = onRejected(this.data) // 如果onRejected的返回值是一個Promise對象,直接取它的結果做為返回promise實例的結果 if(result instanceof MyPromise) { result.then(resolve, reject) } } catch (e) { reject(e) } }) } if (this.status === 'pending') { /** * 如果當前的Promise還處於pending狀態,我們並不能確定調用onResolved還是onRejected, * 只能等到Promise的狀態確定后,才能確實如何處理。 * 所以我們需要把以上兩種情況的處理邏輯做為callback放入promise1(此處即this)的回調數組里 * 具體邏輯也與上面類似 */ return new MyPromise((resolve, reject) => { this.onResolvedCallback.push((value) => { try { let result = onResolved(this.data); if (result instanceof MyPromise) { result.then(resolve, reject) } } catch (e) { reject(e) } }) this.onRejectedCallback.push(reason => { try { let result = onRejected(this.data) if (result instanceof MyPromise) { result.then(resolve, reject) } } catch (e) { reject(e) } }) }