原文連結史上最最最詳細的手寫Promise教程
我們工作中免不了運用promise用來解決非同步回撥問題。平時用的很多庫或者外掛都運用了promise 例如axios、fetch等等。但是你知道promise是咋寫出來的呢?
別怕~這裡有本promisesA+規範,便宜點10元賣給你了。
1、Promise 的宣告
首先呢,promise肯定是一個類,我們就用class來宣告。
-
由於
new Promise((resolve, reject)=>{})
,所以傳入一個引數(函式),祕籍裡叫他executor,傳入就執行。 -
executor裡面有兩個引數,一個叫resolve(成功),一個叫reject(失敗)。
-
由於resolve和reject可執行,所以都是函式,我們用let宣告。
classPromise{ // 構造器constructor(executor){ // 成功let resolve = () => { }; // 失敗let reject = () => { }; // 立即執行 executor(resolve, reject); } }
解決基本狀態
祕籍對Promise有規定:
Promise存在三個狀態(state)pending、fulfilled、rejected
pending(等待態)為初始態,並可以轉化為fulfilled(成功態)和rejected(失敗態)
成功時,不可轉為其他狀態,且必須有一個不可改變的值(value)
失敗時,不可轉為其他狀態,且必須有一個不可改變的原因(reason)
new Promise((resolve, reject)=>{resolve(value)})
resolve為成功,接收引數value,狀態改變為fulfilled,不可再次改變。
new Promise((resolve, reject)=>{reject(reason)})
reject為失敗,接收引數reason,狀態改變為rejected,不可再次改變。
若是executor函式報錯 直接執行reject();
於是乎,我們獲得以下程式碼
classPromise{
constructor(executor){
// 初始化state為等待態this.state = 'pending';
// 成功的值this.value = undefined;
// 失敗的原因this.reason = undefined;
let resolve = value => {
// state改變,resolve呼叫就會失敗if (this.state === 'pending') {
// resolve呼叫後,state轉化為成功態this.state = 'fulfilled';
// 儲存成功的值this.value = value;
}
};
let reject = reason => {
// state改變,reject呼叫就會失敗if (this.state === 'pending') {
// reject呼叫後,state轉化為失敗態this.state = 'rejected';
// 儲存失敗的原因this.reason = reason;
}
};
// 如果executor執行報錯,直接執行rejecttry{
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
}
複製程式碼
then方法
祕籍規定:Promise有一個叫做then的方法,裡面有兩個引數:onFulfilled,onRejected,成功有成功的值,失敗有失敗的原因
-
當狀態state為fulfilled,則執行onFulfilled,傳入this.value。當狀態state為rejected,則執行onRejected,傳入this.value
-
onFulfilled,onRejected如果他們是函式,則必須分別在fulfilled,rejected後被呼叫,value或reason依次作為他們的第一個引數
classPromise{ constructor(executor){...} // then 方法 有兩個引數onFulfilled onRejected then(onFulfilled,onRejected) { // 狀態為fulfilled,執行onFulfilled,傳入成功的值if (this.state === 'fulfilled') { onFulfilled(this.value); }; // 狀態為rejected,執行onRejected,傳入失敗的原因if (this.state === 'rejected') { onRejected(this.reason); }; } }
這下武學初成,可以對付對付江湖小雜毛了,但是對於帶setTimeout的江洋大盜還是沒轍。
解決非同步實現
現在基本可以實現簡單的同步程式碼,但是當resolve在setTomeout內執行,then時state還是pending等待狀態 我們就需要在then呼叫的時候,將成功和失敗存到各自的陣列,一旦reject或者resolve,就呼叫它們
類似於釋出訂閱,先將then裡面的兩個函式儲存起來,由於一個promise可以有多個then,所以存在同一個陣列內。
// 多個then的情況let p = newPromise();
p.then();
p.then();
複製程式碼
成功或者失敗時,forEach呼叫它們
classPromise{
constructor(executor){
this.state = 'pending';
this.value = undefined;
this.reason = undefined;
// 成功存放的陣列this.onResolvedCallbacks = [];
// 失敗存放法陣列this.onRejectedCallbacks = [];
let resolve = value => {
if (this.state === 'pending') {
this.state = 'fulfilled';
this.value = value;
// 一旦resolve執行,呼叫成功陣列的函式this.onResolvedCallbacks.forEach(fn=>fn());
}
};
let reject = reason => {
if (this.state === 'pending') {
this.state = 'rejected';
this.reason = reason;
// 一旦reject執行,呼叫失敗陣列的函式this.onRejectedCallbacks.forEach(fn=>fn());
}
};
try{
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
then(onFulfilled,onRejected) {
if (this.state === 'fulfilled') {
onFulfilled(this.value);
};
if (this.state === 'rejected') {
onRejected(this.reason);
};
// 當狀態state為pending時if (this.state === 'pending') {
// onFulfilled傳入到成功陣列this.onResolvedCallbacks.push(()=>{
onFulfilled(this.value);
})
// onRejected傳入到失敗陣列this.onRejectedCallbacks.push(()=>{
onRejected(this.value);
})
}
}
}
複製程式碼
解決鏈式呼叫
我門常常用到new Promise().then().then()
,這就是鏈式呼叫,用來解決回撥地獄
1、為了達成鏈式,我們預設在第一個then裡返回一個promise。祕籍規定了一種方法,就是在then裡面返回一個新的promise,稱為promise2:promise2 = new Promise((resolve, reject)=>{})
- 將這個promise2返回的值傳遞到下一個then中
- 如果返回一個普通的值,則將普通的值傳遞給下一個then中
2、當我們在第一個then中return
了一個引數(引數未知,需判斷)。這個return出來的新的promise就是onFulfilled()或onRejected()的值
祕籍則規定onFulfilled()或onRejected()的值,即第一個then返回的值,叫做x,判斷x的函式叫做resolvePromise
-
首先,要看x是不是promise。
-
如果是promise,則取它的結果,作為新的promise2成功的結果
-
如果是普通值,直接作為promise2成功的結果
-
所以要比較x和promise2
-
resolvePromise的引數有promise2(預設返回的promise)、x(我們自己
return
的物件)、resolve、reject -
resolve和reject是promise2的
classPromise{ constructor(executor){ this.state = 'pending'; this.value = undefined; this.reason = undefined; this.onResolvedCallbacks = []; this.onRejectedCallbacks = []; let resolve = value => { if (this.state === 'pending') { this.state = 'fulfilled'; this.value = value; this.onResolvedCallbacks.forEach(fn=>fn()); } }; let reject = reason => { if (this.state === 'pending') { this.state = 'rejected'; this.reason = reason; this.onRejectedCallbacks.forEach(fn=>fn()); } }; try{ executor(resolve, reject); } catch (err) { reject(err); } } then(onFulfilled,onRejected) { // 宣告返回的promise2let promise2 = newPromise((resolve, reject)=>{ if (this.state === 'fulfilled') { let x = onFulfilled(this.value); resolvePromise(promise2, x, resolve, reject); }; if (this.state === 'rejected') { let x = onRejected(this.reason); resolvePromise(promise2, x, resolve, reject); }; if (this.state === 'pending') { this.onResolvedCallbacks.push(()=>{ let x = onFulfilled(this.value); resolvePromise(promise2, x, resolve, reject); }) this.onRejectedCallbacks.push(()=>{ let x = onRejected(this.value); resolvePromise(promise2, x, resolve, reject); }) } }); // 返回promise,完成鏈式return promise2; } }
完成resolvePromise函式
祕籍規定了一段程式碼,讓不同的promise程式碼互相套用,叫做resolvePromise
-
如果 x === promise2,則是會造成迴圈引用,自己等待自己完成,則報“迴圈引用”錯誤
let p = newPromise(resolve => { resolve(0); }); var p2 = p.then(data => { // 迴圈引用,自己等待自己完成,一輩子完不成return p2; })
1、判斷x
-
Otherwise, if x is an object or function,Let then be x.then
-
x 不能是null
-
x 是普通值 直接resolve(x)
-
x 是物件或者函式(包括promise),
let then = x.then
2、當x是物件或者函式(預設promise) -
宣告瞭then
-
如果取then報錯,則走reject()
-
如果then是個函式,則用call執行then,第一個引數是this,後面是成功的回撥和失敗的回撥
-
如果成功的回撥還是pormise,就遞迴繼續解析 3、成功和失敗只能呼叫一個 所以設定一個called來防止多次呼叫
functionresolvePromise(promise2, x, resolve, reject){ // 迴圈引用報錯if(x === promise2){ // reject報錯return reject(newTypeError('Chaining cycle detected for promise')); } // 防止多次呼叫let called; // x不是null 且x是物件或者函式if (x != null && (typeof x === 'object' || typeof x === 'function')) { try { // A+規定,宣告then = x的then方法let then = x.then; // 如果then是函式,就預設是promise了if (typeof then === 'function') { // 就讓then執行 第一個引數是this 後面是成功的回撥 和 失敗的回撥 then.call(x, y => { // 成功和失敗只能呼叫一個if (called) return; called = true; // resolve的結果依舊是promise 那就繼續解析 resolvePromise(promise2, y, resolve, reject); }, err => { // 成功和失敗只能呼叫一個if (called) return; called = true; reject(err);// 失敗了就失敗了 }) } else { resolve(x); // 直接成功即可 } } catch (e) { // 也屬於失敗if (called) return; called = true; // 取then出錯了那就不要在繼續執行了 reject(e); } } else { resolve(x); } }
解決其他問題
1、祕籍規定onFulfilled,onRejected都是可選引數,如果他們不是函式,必須被忽略
-
onFulfilled返回一個普通的值,成功時直接等於
value => value
-
onRejected返回一個普通的值,失敗時如果直接等於 value => value,則會跑到下一個then中的onFulfilled中,所以直接扔出一個錯誤
reason => throw err
2、祕籍規定onFulfilled或onRejected不能同步被呼叫,必須非同步呼叫。我們就用setTimeout解決非同步問題 -
如果onFulfilled或onRejected報錯,則直接返回reject()
classPromise{ constructor(executor){ this.state = 'pending'; this.value = undefined; this.reason = undefined; this.onResolvedCallbacks = []; this.onRejectedCallbacks = []; let resolve = value => { if (this.state === 'pending') { this.state = 'fulfilled'; this.value = value; this.onResolvedCallbacks.forEach(fn=>fn()); } }; let reject = reason => { if (this.state === 'pending') { this.state = 'rejected'; this.reason = reason; this.onRejectedCallbacks.forEach(fn=>fn()); } }; try{ executor(resolve, reject); } catch (err) { reject(err); } } then(onFulfilled,onRejected) { // onFulfilled如果不是函式,就忽略onFulfilled,直接返回value onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value; // onRejected如果不是函式,就忽略onRejected,直接扔出錯誤 onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err }; let promise2 = newPromise((resolve, reject) => { if (this.state === 'fulfilled') { // 非同步 setTimeout(() => { try { let x = onFulfilled(this.value); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }, 0); }; if (this.state === 'rejected') { // 非同步 setTimeout(() => { // 如果報錯try { let x = onRejected(this.reason); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }, 0); }; if (this.state === 'pending') { this.onResolvedCallbacks.push(() => { // 非同步 setTimeout(() => { try { let x = onFulfilled(this.value); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }, 0); }); this.onRejectedCallbacks.push(() => { // 非同步 setTimeout(() => { try { let x = onRejected(this.reason); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }, 0) }); }; }); // 返回promise,完成鏈式return promise2; } }
大功告成
順便附贈catch和resolve、reject、race、all方法 classPromise{ constructor(executor){ this.state = 'pending'; this.value = undefined; this.reason = undefined; this.onResolvedCallbacks = []; this.onRejectedCallbacks = []; let resolve = value => { if (this.state === 'pending') { this.state = 'fulfilled'; this.value = value; this.onResolvedCallbacks.forEach(fn=>fn()); } }; let reject = reason => { if (this.state === 'pending') { this.state = 'rejected'; this.reason = reason; this.onRejectedCallbacks.forEach(fn=>fn()); } }; try{ executor(resolve, reject); } catch (err) { reject(err); } } then(onFulfilled,onRejected) { onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value; onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err }; let promise2 = newPromise((resolve, reject) => { if (this.state === 'fulfilled') { setTimeout(() => { try { let x = onFulfilled(this.value); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }, 0); }; if (this.state === 'rejected') { setTimeout(() => { try { let x = onRejected(this.reason); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }, 0); }; if (this.state === 'pending') { this.onResolvedCallbacks.push(() => { setTimeout(() => { try { let x = onFulfilled(this.value); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }, 0); }); this.onRejectedCallbacks.push(() => { setTimeout(() => { try { let x = onRejected(this.reason); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }, 0) }); }; }); return promise2; } catch(fn){ returnthis.then(null,fn); } } functionresolvePromise(promise2, x, resolve, reject){ if(x === promise2){ return reject(newTypeError('Chaining cycle detected for promise')); } let called; if (x != null && (typeof x === 'object' || typeof x === 'function')) { try { let then = x.then; if (typeof then === 'function') { then.call(x, y => { if(called)return; called = true; resolvePromise(promise2, y, resolve, reject); }, err => { if(called)return; called = true; reject(err); }) } else { resolve(x); } } catch (e) { if(called)return; called = true; reject(e); } } else { resolve(x); } } //resolve方法Promise.resolve = function(val){ returnnewPromise((resolve,reject)=>{ resolve(val) }); } //reject方法Promise.reject = function(val){ returnnewPromise((resolve,reject)=>{ reject(val) }); } //race方法 Promise.race = function(promises){ returnnewPromise((resolve,reject)=>{ for(let i=0;i<promises.length;i++){ promises[i].then(resolve,reject) }; }) } //all方法(獲取所有的promise,都執行then,把結果放到陣列,一起返回)Promise.all = function(promises){ let arr = []; let i = 0; functionprocessData(index,data){ arr[index] = data; i++; if(i == promises.length){ resolve(arr); }; }; returnnewPromise((resolve,reject)=>{ for(let i=0;i<promises.length;i++){ promises[i].then(data=>{ processData(i,data); },reject); }; }); }如何驗證我們的promise是否正確
1、先在後面加上下述程式碼
2、npm 有一個promises-aplus-tests外掛 npm i promises-aplus-tests -g 可以全域性安裝 mac使用者最前面加上sudo
3、命令列 promises-aplus-tests [js檔名] 即可驗證
// 目前是通過他測試 他會測試一個物件// 語法糖Promise.defer = Promise.deferred = function () {
let dfd = {}
dfd.promise = newPromise((resolve,reject)=>{
dfd.resolve = resolve;
dfd.reject = reject;
});
return dfd;
}
module.exports = Promise;
//npm install promises-aplus-tests 用來測試自己的promise 符不符合promisesA+規範複製程式碼