Promise主要作用:把異步操作變?yōu)楹唵慰煽兀谕瓿僧惒讲僮骱笸ㄟ^then可以做你想的操作通今。解決了地獄回調(diào)的痛點(diǎn)粥谬。
本篇實(shí)現(xiàn)了Promise
的基本方法resolve
、reject
.then()
辫塌、.then().then()
鏈?zhǔn)秸{(diào)用漏策。
一、Promise概念原理:
遵循Promise/A+規(guī)則:https://promisesaplus.com
它代表了一個異步操作的最終完成或者失敗臼氨。只接受一個函數(shù)(內(nèi)部叫executor
自動執(zhí)行)掺喻,并且函數(shù)有兩個參數(shù):resolve(成功)、reject(失敶⒕亍)感耙。
記住,只要聲明了一個Promise即new Promise()
持隧,他就會立即執(zhí)行抑月!
執(zhí)行時內(nèi)部有三種狀態(tài),pending表示等待中舆蝴、fulfilled表示成功谦絮、rejected表示失敗题诵。
它的結(jié)果是一個明確的狀態(tài):
- pending => fulfilled
- pending => rejected
聽起來它的內(nèi)部有點(diǎn)像薛定諤的貓
只要成功了,永遠(yuǎn)都是成功层皱,失敗了永遠(yuǎn)都是失敗性锭,狀態(tài)已經(jīng)改變就不能再改變。
二叫胖、基本使用:
1草冈、一般我們使用它都需要與.then()方法一起食用,其中.then()也是接受兩個可選的參數(shù):fulfilled(promise成功時候的回調(diào)函數(shù))瓮增、rejected(promise失敗時候的回調(diào)函數(shù))怎棱。如果傳入then的參數(shù)不是一個函數(shù),則會忽略它绷跑。
// promise.then語法
new Promise((resolve, reject) => {
reject(1); // 或是resolve(1)
}).then(res => {
console.log('fulfillment', res);
}, err => {
console.log('rejection', err);
});
// resolve表示成功拳恋,可以通過.then()方法獲取到成功的結(jié)果
new Promise((resolve, reject) => {
resolve(1);
}).then(res => console.log(res)); // 1
// reject表示失敗,使用.catch()方法獲取到失敗錯誤原因
new Promise((resolve, reject) => {
reject(2);
}).catch(err => console.log(err)); // 2
2砸捏、支持鏈?zhǔn)绞褂?/p>
new Promise((resolve, reject) => {
resolve(1);
}).then(res => res).then(res => console.log(res)); // 1
//如果不return一個結(jié)果谬运,默認(rèn)返回的是一個undefind
new Promise((resolve, reject) => {
resolve(1);
}).then(res => {}).then(res => console.log(res)); // undefind
三、實(shí)現(xiàn)環(huán)節(jié)
1垦藏、初步結(jié)構(gòu)
按照上面我們已經(jīng)知道Promise內(nèi)部的一些內(nèi)容梆暖,寫一個方法架構(gòu)出來。
它需要有:三種狀態(tài)(pending掂骏、fulfilled轰驳、rejected)、默認(rèn)值(成功/失敗的默認(rèn)返回值undefind)弟灼、默認(rèn)方法(executor滑废、resolve、reject 袜爪、then)先寫上去。
class MyPromise {
/**
* 內(nèi)部三種狀態(tài)
* @type {string}
*/
static pending = 'pending';
static fulfilled = 'fulfilled';
static rejected = 'rejected';
constructor(executor) {
this.status = MyPromise.pending; //默認(rèn)的狀態(tài)是pending
this.susecessValue = undefined; // 成功默認(rèn)返回是undefined
this.failedValue = undefined; // 失敗默認(rèn)返回是undefined
executor(this.resolve, this.reject); //自動執(zhí)行
}
/**
* 成功的方法
* @param res
*/
resolve = (res) => {
};
/**
* 失敗的方法
* @param err
*/
reject = (err) => {
};
/**
* 獲取成功結(jié)果的then方法
* @param onResolved
* @param onRejected
*/
then = (onResolved, onRejected) => {
};
}
2薛闪、接著把resolve、reject 、then方法補(bǔ)充完整疹尾,很簡單秘豹,只需要判斷它的狀態(tài)。resolve把狀態(tài)改為fulfilled成功诱咏,同時把值存起來苔可;reject 把狀態(tài)改為rejected,同時把值存起來袋狞。then方法分別判斷焚辅,是成功還是還是失敗映屋,去運(yùn)行then((res)=>{})
傳進(jìn)來的方法就可以。
class MyPromise {
static pending = 'pending';
static fulfilled = 'fulfilled';
static rejected = 'rejected';
constructor(executor) {
this.status = MyPromise.pending;
this.susecessValue = undefined;
this.failedValue = undefined;
executor(this.resolve, this.reject);
}
resolve = (res) => {
if (this.status === 'pending') {
this.susecessValue = res;
this.status = MyPromise.fulfilled; // 成功了直接切換狀態(tài)同蜻,因?yàn)閠hen()方法需要使用狀態(tài)
}
};
reject = (err) => {
if (this.status === 'pending') {
this.failedValue = err;
this.status = MyPromise.rejected; // 失敗了直接切換狀態(tài)棚点,因?yàn)閠hen()方法需要使用狀態(tài)
}
};
then = (onResolved, onRejected) => {
if (this.status === 'fulfilled' && onResolved) {
onResolved(this.susecessValue);
}
if (this.status === 'rejected' && onRejected) {
onRejected(this.failedValue);
}
};
}
到這里算是最基本的一個promise完成了,我們來運(yùn)行看一下:
new MyPromise((resolve, reject) => {
resolve('成功1111');
}).then(res => {
console.log('fulfillment', res); // fulfillment 成功1111
}, err => {
console.log('rejection', err);
});
new MyPromise((resolve, reject) => {
reject('失敗222');
}).then(res => {
console.log('fulfillment', res);
}, err => {
console.log('rejection', err); // rejection 失敗222
});
通過運(yùn)行是沒有問題的湾蔓,到這里我們算是完成了第一個小目標(biāo)瘫析。
但是如果此時我們碰到了setTimeout
就會有很大麻煩。比如resolve
是寫在setTimeout
里的:
new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve('測試');
}, 3000);
}).then(res => {
console.log('fulfillment', res);
}, err => {
console.log('rejection', err);
});
我們會發(fā)現(xiàn)該方法并不會運(yùn)行默责,原因很簡單贬循,因?yàn)榇藭r的resolve('測試')
方法被延遲了3秒才執(zhí)行,然而我們寫的then是沒有被延遲的桃序,所以這時候杖虾,then()
方法會先執(zhí)行,但是因?yàn)槲覀兊?code>then內(nèi)部寫了this.status
狀態(tài)判斷(此時resolve還沒有把默認(rèn)的狀態(tài)status
從pending
改為fulfilled
)所以它不執(zhí)行內(nèi)部的任何一個方法葡缰。
3亏掀、增加支持異步(重點(diǎn)難點(diǎn))
我們可以把then(x = > x, y=> x)
方法中的兩個參數(shù)(都是函數(shù))先存起來。等待resolve
或者reject
的時候再運(yùn)行泛释。
class MyPromise {
static pending = 'pending';
static fulfilled = 'fulfilled';
static rejected = 'rejected';
constructor(executor) {
this.onFulfilledFoo = []; // 存儲成功的方法
this.onRejectedFoo = []; // 存儲失敗的方法
this.status = MyPromise.pending;
this.susecessValue = undefined;
this.failedValue = undefined;
executor(this.resolve, this.reject);
}
resolve = (res) => {
if (this.status === 'pending') {
this.susecessValue = res;
this.status = MyPromise.fulfilled;
// resolve 時運(yùn)行我們存儲過的對應(yīng)onResolved方法,注意要把參數(shù)傳遞給fn
this.onFulfilledFoo.forEach(fn => fn(res));
}
};
reject = (err) => {
if (this.status === 'pending') {
this.failedValue = err;
this.status = MyPromise.rejected;
// 同理滤愕,reject時就去運(yùn)行我們存儲起來的對應(yīng)onRejected方法,注意要把參數(shù)傳遞給fn
this.onRejectedFoo.forEach(fn => fn(err));
}
};
then = (onResolved, onRejected) => {
// 如果是pending狀態(tài),把成功的方法通過數(shù)組存起來
if (this.status === 'pending' && onResolved) {
this.onFulfilledFoo.push(onResolved);
}
// 如果是pending狀態(tài)怜校,把失敗的方法通過數(shù)組存起來
if (this.status === 'pending' && onRejected) {
this.onRejectedFoo.push(onRejected);
}
if (this.status === 'fulfilled' && onResolved) {
onResolved(this.susecessValue);
}
if (this.status === 'rejected' && onRejected) {
onRejected(this.failedValue);
}
};
}
然后再來測試下我們剛才遇到的 setTimeout
問題:
new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve('測試');
}, 3000);
}).then(res => {
console.log('fulfillment', res); // 測試
}, err => {
console.log('rejection', err);
});
很完美间影,3秒后打印出‘測試’兩個字,這就是我們要的結(jié)果茄茁。但是此時還有一個大問題魂贬,就是我們還不能支持鏈?zhǔn)秸{(diào)用
。比如說以下這種情況:
function foo() {
return new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve('返回結(jié)果');
}, 3000);
});
}
foo().then(res => {
console.log('1', res);
return res;
}).then(res => res); //這里的then會報錯:Cannot read properties of undefined (reading 'then')
我們想到是否可以直接在我們寫的promise中then方法直接return一個this
裙顽,是可以的付燥。但是解決不了問題。因?yàn)橛龅揭韵逻@個情況就涼涼了:
function foo() {
return new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve('測試');
}, 3000);
});
}
foo().then(res => {
return res + '1111';
}).then(res => console.log(res)); // 測試
// 正確結(jié)果應(yīng)該是:測試1111
很顯然這種結(jié)果不是我們想要的愈犹,因?yàn)槲覀兊膖hen執(zhí)行完后沒有把后續(xù)的then繼續(xù)把方法放到promise里面執(zhí)行(理解為現(xiàn)在的結(jié)果是在promise外部執(zhí)行的)键科。所以我們需要在任何時候都返回一個promise,以供then鏈?zhǔn)秸{(diào)用漩怎。
4勋颖、增加支持鏈?zhǔn)秸{(diào)用(重點(diǎn))
我們需要先改造一下剛剛的寫法,1勋锤、then永遠(yuǎn)返回類實(shí)例本身(只有這樣才可以為無限.then().then()
調(diào)用)饭玲;如果是第一次調(diào)用then(即狀態(tài)為pending,此時promise內(nèi)部還沒給出結(jié)果) 就把then方法用來做存儲接收到的方法操作叁执,即外部傳進(jìn)來來的方法都是用then存儲茄厘。 否則就直接執(zhí)行_handle方法(即異步完成了矮冬,后面的.then可以直接執(zhí)行),2蚕断、resolve欢伏、reject
負(fù)傳遞剛剛通過then存儲的傳進(jìn)來的方法給_handle
,3(重點(diǎn))亿乳、_handle
負(fù)責(zé)判斷成功或者失敗情況下該執(zhí)行哪個對應(yīng)的方法硝拧。并且多了一步把then傳進(jìn)來的方法里面添加計(jì)算好的返回值。
class MyPromise {
static pending = 'pending';
static fulfilled = 'fulfilled';
static rejected = 'rejected';
constructor(executor) {
this.saveFoo = []; // 只需要一個就可以存儲
this.status = MyPromise.pending;
this.susecessValue = undefined;
this.failedValue = undefined;
executor(this.resolve, this.reject);
}
resolve = (res) => {
if (this.status === 'pending') {
this.susecessValue = res;
this.status = MyPromise.fulfilled;
// 此時調(diào)用我們的handle去觸發(fā)外部傳進(jìn)來的方法
this.saveFoo.forEach(fn => this._handle(fn));
}
};
reject = (err) => {
if (this.status === 'pending') {
this.failedValue = err;
this.status = MyPromise.rejected;
this.saveFoo.forEach(fn => this._handle(fn)); //同理
}
};
/**
* 如果是pending狀態(tài)(即第一次調(diào)用then葛假,promise內(nèi)部還沒給出結(jié)果)
* 就把then方法用來做存儲接收到的方法操作障陶,即外部傳進(jìn)來來的方法都是用then存儲
* 否則就直接執(zhí)行_handle方法(即異步完成了,后面的.then可以直接執(zhí)行)
* @param onResolved
* @param onRejected
*/
then = (onResolved, onRejected) => {
return new MyPromise((nextResolve, nexReject) => {
if (this.status === 'pending') {
this.saveFoo.push({onResolved, onRejected, nextResolve, nexReject});
} else {
this._handle({onResolved, onRejected, nextResolve, nexReject});
}
});
};
/**
* 負(fù)責(zé)執(zhí)行成功或者失敗后的方法
* @param callbacks
* @returns {MyPromise}
* @private
*/
_handle = (callbacks) => {
const { onResolved, onRejected, nextResolve, nexReject } = callbacks;
if (this.status === 'fulfilled' && onResolved) {
// 判斷是否有方法聊训,有的話和我們最開始的一樣傳入值抱究,否則返回默認(rèn)值
const thenValue = onResolved ? onResolved(this.susecessValue) : this.susecessValue;
// 把最開始得到的值繼續(xù)傳遞給下一個then中傳遞進(jìn)來的方法
nextResolve(thenValue);
}
// 同理
if (this.status === 'rejected' && onRejected) {
const thenErr = onRejected ? onRejected(this.failedValue) : this.failedValue;
nexReject(thenErr);
}
};
}
再次測試一下:
function foo() {
return new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve('測試');
}, 3000);
});
}
foo().then(res => {
console.log('結(jié)果:', res);
return res + '1111';
}).then(res => console.log('結(jié)果:', res));
// 結(jié)果: 測試
// 結(jié)果: 測試1111
到這里,我們算是解決了then示例調(diào)用的難題带斑,還有一種情況鼓寺,就是當(dāng)我們的第二個then里面resolve的是一個Promise而不是一個值的時候,比如:
function foo() {
return new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve('測試');
}, 3000);
});
}
foo().then(res => {
return new MyPromise((resolve, reject) => { // 如果這里再返回一個promise勋磕,我們就需要判斷
resolve('測試222');
});
}).then(res => console.log('結(jié)果:', res));
我們就需要對它判斷一下妈候,如果是一個promise實(shí)例就直接去resolve或者reject返回結(jié)果,于是代碼里面加個promiseToThen
方法判斷是否是實(shí)例本身挂滓,是的話就去走then方法返回結(jié)果苦银。
class MyPromise {
static pending = 'pending';
static fulfilled = 'fulfilled';
static rejected = 'rejected';
constructor(executor) {
this.saveFoo = []; // 只需要一個就可以存儲
this.status = MyPromise.pending;
this.susecessValue = undefined;
this.failedValue = undefined;
executor(this.resolve, this.reject);
}
resolve = (res) => {
this.promiseToThen(res); // 判斷是否是一個promise
if (this.status === 'pending') {
this.susecessValue = res;
this.status = MyPromise.fulfilled;
this.saveFoo.forEach(fn => this._handle(fn));
}
};
reject = (err) => {
this.promiseToThen(err); // 判斷是否是一個promise
if (this.status === 'pending') {
this.failedValue = err;
this.status = MyPromise.rejected;
this.saveFoo.forEach(fn => this._handle(fn)); //同理
}
};
/**
* 如果是一個promise的本身實(shí)例就走then
* @param Params
*/
promiseToThen = (Params) => {
if (Params instanceof MyPromise) {
Params.then(this.resolve, this.reject);
}
};
/**
* 如果是pending狀態(tài)(即第一次調(diào)用then,promise內(nèi)部還沒給出結(jié)果)
* 就把then方法用來做存儲接收到的方法操作赶站,即外部傳進(jìn)來來的方法都是用then存儲
* 否則就直接執(zhí)行_handle方法(即異步完成了幔虏,后面的.then可以直接執(zhí)行)
* @param onResolved
* @param onRejected
*/
then = (onResolved, onRejected) => {
return new MyPromise((nextResolve, nexReject) => {
if (this.status === 'pending') {
this.saveFoo.push({onResolved, onRejected, nextResolve, nexReject});
} else {
this._handle({onResolved, onRejected, nextResolve, nexReject});
}
});
};
/**
* 負(fù)責(zé)執(zhí)行成功或者失敗后的方法
* @param callbacks
* @returns {MyPromise}
* @private
*/
_handle = (callbacks) => {
const { onResolved, onRejected, nextResolve, nexReject } = callbacks;
if (this.status === 'fulfilled' && onResolved) {
// 判斷是否有方法,有的話和我們最開始的一樣傳入值贝椿,否則返回默認(rèn)值
const thenValue = onResolved ? onResolved(this.susecessValue) : this.susecessValue;
// 把最開始得到的值繼續(xù)傳遞給下一個then中傳遞進(jìn)來的方法
nextResolve(thenValue);
}
// 同理
if (this.status === 'rejected' && onRejected) {
const thenErr = onRejected ? onRejected(this.failedValue) : this.failedValue;
nexReject(thenErr);
}
};
}
最后想括,我們還有一個步驟沒做,就是promise規(guī)定烙博,如果then的參數(shù)不是一個函數(shù)而是一個具體的值瑟蜈,我們就需要忽略并且返回對應(yīng)的結(jié)果;如果then()參數(shù)為空那么這個then可以不執(zhí)行习勤,直接返回對應(yīng)結(jié)果就可以。比如
.then(res=>'下一個then返回一個具體數(shù)值').then(1).then(res=>console.log(res))
// 或者是這種情況
.then(res=>'下一個then參數(shù)為空').then().then(res=>console.log(res))
我們只需要在_handle
方法處加上判斷即可焙格,最終代碼如下:
class MyPromise {
static pending = 'pending';
static fulfilled = 'fulfilled';
static rejected = 'rejected';
constructor(executor) {
this.saveFoo = []; // 只需要一個就可以存儲
this.status = MyPromise.pending;
this.susecessValue = undefined;
this.failedValue = undefined;
try {
executor(this.resolve, this.reject);
} catch (e) {
this.reject(e)
}
}
resolve = (res) => {
this.promiseToThen(res); // 判斷是否是一個promise
if (this.status === 'pending') {
this.susecessValue = res;
this.status = MyPromise.fulfilled;
this.saveFoo.forEach(fn => this._handle(fn));
}
};
reject = (err) => {
this.promiseToThen(err); // 判斷是否是一個promise
if (this.status === 'pending') {
this.failedValue = err;
this.status = MyPromise.rejected;
this.saveFoo.forEach(fn => this._handle(fn)); //同理
}
};
/**
* 如果是一個promise的本身實(shí)例就走then
* @param Params
*/
promiseToThen = (Params) => {
if (Params instanceof MyPromise) {
Params.then(this.resolve, this.reject);
}
};
/**
* 把then方法用來做存儲接收到的方法操作图毕,即外部傳進(jìn)來來的方法都是用then存儲
* @param onResolved
* @param onRejected
*/
then = (onResolved, onRejected) => {
return new MyPromise((nextResolve, nexReject) => {
if (this.status === 'pending') {
this.saveFoo.push({ onResolved, onRejected, nextResolve, nexReject });
}
this._handle({ onResolved, onRejected, nextResolve, nexReject });
});
};
_handle = (callbacks) => {
const {onResolved, onRejected, nextResolve, nexReject} = callbacks;
if (typeof onResolved === 'undefined') { // 直接執(zhí)行下一個then方法中的函數(shù)
typeof nextResolve === 'function' && nextResolve(this.susecessValue)
}
/**
* 負(fù)責(zé)執(zhí)行成功或者失敗后的方法
* @param callbacks
* @returns {MyPromise}
* @private
*/
if (this.status === 'fulfilled' && onResolved) {
// 如果參數(shù)不是一個函數(shù)
const translateFun = typeof onResolved === 'function' ? onResolved : () => onResolved;
// 判斷是否有方法,有的話和我們最開始的一樣傳入值眷唉,否則返回默認(rèn)值
const thenValue = onResolved ? translateFun(this.susecessValue) : this.susecessValue;
// 把最開始得到的值繼續(xù)傳遞給下一個then中傳遞進(jìn)來的方法
nextResolve(thenValue);
}
// 同理
if (this.status === 'rejected' && onRejected) {
const translateFun = typeof onRejected === 'function' ? onRejected : () => onRejected;
const thenErr = onRejected ? translateFun(this.failedValue) : this.failedValue;
nexReject(thenErr);
}
};
}
最后:提示:任何手寫的promise無法達(dá)到與原生的promise的運(yùn)行順序一致的效果(但是我們能保證then是在異步回調(diào)后出發(fā)予颤,這其實(shí)是promise的一個重要點(diǎn))囤官,因?yàn)樵膒romise里.then方法
會被歸類到微任務(wù)那里,導(dǎo)致最終運(yùn)行順序不一致類似這種蛤虐。
new Promise(resolve=>resolve(1)).then(res=>console.log(1))
console.log(2)
// 2 1
new MyPromise(resolve=>resolve(1)).then(res=>console.log(1))
console.log(2)
// 1 2