最近一直私下在看Android項(xiàng)目横朋,前端這一塊沒(méi)怎么仔細(xì)研究。昨天在寫(xiě)重構(gòu)公司前端項(xiàng)目的時(shí)候,我發(fā)現(xiàn)一旦有異步的任務(wù)璧眠,腦海里面條件反射一般的出現(xiàn)promise的字樣。重構(gòu)的多了 心就就在納悶:既然promise這么好用读虏,我能不能自己手寫(xiě)一個(gè)promise呢责静?我思索了半天,按照自己的想法模擬了出來(lái)掘譬,但是和一位大佬交流的時(shí)候泰演,他說(shuō)我的寫(xiě)法并沒(méi)有遵循Promise/A+規(guī)范。當(dāng)時(shí)我心里是懵逼的葱轩,加班的時(shí)候就一直想寫(xiě)這個(gè)問(wèn)題睦焕。
一、 promise是什么靴拱?
1.1垃喊、 為什么要用promise?
我們都知道袜炕,在javascript里面所有的代碼都是單線程執(zhí)行的本谜。由于這種“單線程”的弊端,所有javascript重的網(wǎng)絡(luò)請(qǐng)求偎窘、瀏覽器事件等都只能用異步來(lái)執(zhí)行乌助。傳統(tǒng)的異步實(shí)現(xiàn)的方式都是用回調(diào)函數(shù)來(lái)實(shí)現(xiàn)的溜在,例如:
request.onreadystatechange = function () {
if (request.readyState === 4) {
if (request.status === RESULT_OK) {
return success(req.responseText);
}
return fail(req.status);
}
}
這樣雖然比較直觀,但是并不符合程序員的審美習(xí)慣他托,代碼的復(fù)用性也很低掖肋。按照現(xiàn)在主流的代碼風(fēng)格來(lái)說(shuō),一份優(yōu)秀的代碼模版就應(yīng)該是鏈表形式的赏参。
// Android寫(xiě)習(xí)慣了志笼,一般封裝網(wǎng)絡(luò)都是okhttp 。不用理我 = =
okhttp.request()
.onSuccesul()
.onFail();
于是promise應(yīng)運(yùn)而生把篓。
1.2纫溃、 promise介紹
promise的中文釋意為:承諾。我覺(jué)得這個(gè)翻譯很能凸顯這個(gè)對(duì)象的含義韧掩。
promise里面有三個(gè)狀態(tài):
- pending:進(jìn)行中
- fulfilled: 已成功
- rejected:已結(jié)束
為什么說(shuō)紊浩,承諾這個(gè)解釋很能凸顯promise對(duì)象的特性呢?我們來(lái)為您談?wù)刾romise里面的兩個(gè)特點(diǎn):
1揍很、 對(duì)象不能被任何手段來(lái)更改郎楼。怎么解釋呢?只要是你下了承諾窒悔,就不能受任何外界環(huán)境的干擾呜袁,只受到一步操作的結(jié)果來(lái)決定。
2简珠、 一旦狀態(tài)改變阶界,就不會(huì)再變,并且任何時(shí)候都可以得到這個(gè)結(jié)果聋庵。
從上面的特性里面膘融,是不是更加加深了對(duì)承諾的理解呢!
二祭玉、 promise簡(jiǎn)單探究氧映。
前面說(shuō)了這么多,都是傻把式脱货,下面我們就用代碼體驗(yàn)一把promise的快感吧 岛都。
new Promise((resolve,reject)=>{
let randomNumber = Math.random()
console.log(randomNumber)
if(randomNumber>0.5){
resolve('success!!!')
}else{
reject('fail!!!')
}
}).then(res=>{
console.log(res);
},error=>{
console.log(error);
})
從上面的代碼我們可以比較清楚的發(fā)現(xiàn),Promise接收兩個(gè)參數(shù):resolve和reject振峻。resolve函數(shù)的作用是臼疫,將Promise對(duì)象的狀態(tài)從“進(jìn)行中”變?yōu)椤俺晒Α保?pending => resolved),在異步操作成功時(shí)調(diào)用扣孟,并將異步操作的結(jié)果烫堤,作為參數(shù)傳遞出去;reject函數(shù)的作用是,將Promise對(duì)象的狀態(tài)從“進(jìn)行中”變?yōu)椤笆 保?pending=>rejected)鸽斟,在異步操作失敗時(shí)調(diào)用拔创,并將異步操作報(bào)出的錯(cuò)誤,作為參數(shù)傳遞出去湾盗。
并且在Promise對(duì)象實(shí)例生成后伏蚊,可以用then來(lái)分別指定完成后的resolve和reject。并且then里面的reject是可選項(xiàng)格粪。
對(duì)于promise,我覺(jué)得主要有三個(gè)點(diǎn)需要給讀者講一下氛改,來(lái)看下面一段代碼:
let promise = new Promise((resolve,reject)=>{
let randomNumber = Math.random()
console.log(randomNumber)
if(randomNumber>0.5){
resolve('success!!!')
}else{
reject('fail!!!')
}
})
我們發(fā)現(xiàn)單獨(dú)運(yùn)行這一段代碼的話帐萎,console依然被打印出來(lái)了。這說(shuō)明promise是創(chuàng)建的時(shí)候就被運(yùn)行的胜卤。
再來(lái)看一段代碼:
let promise = new Promise((resolve,reject)=>{
let randomNumber = Math.random()
if(randomNumber>0.5){
resolve('success!!!')
}else{
reject('fail!!!')
}
console.log(randomNumber)
}).then(res=>{
console.log(res);
},error=>{
console.log(error)
})
效果如下:
由上面的現(xiàn)象我們可以輕易的看出來(lái):resolve和reject后的代碼一般都還會(huì)運(yùn)行疆导,如果你想避免這種情況,你可以結(jié)合return來(lái)使用葛躏,例如:
return resolve('success!!!');
return reject('fail!!!');
三澈段、 promise重要屬性探究
2.1、 then
Promise 實(shí)例具有then方法舰攒,也就是說(shuō)败富,then方法是定義在原型對(duì)象Promise.prototype上的。它的作用是為 Promise 實(shí)例添加狀態(tài)改變時(shí)的回調(diào)函數(shù)摩窃。then里面有兩個(gè)回調(diào)函數(shù)兽叮,前者返回resolve的回調(diào)函數(shù),后者是可選值猾愿。并且then返回返回一個(gè)新的promise鹦聪,這樣就能一直使用鏈?zhǔn)浇Y(jié)構(gòu)了。
2.2蒂秘、 catch
catch可以看成是then(null/undefined, reject)的別名 專門(mén)用來(lái)指定錯(cuò)誤發(fā)生時(shí)的回調(diào)函數(shù)泽本。那么為什么要設(shè)計(jì)這個(gè)屬性呢?主要有兩個(gè)方面姻僧,首先來(lái)看下面一段代碼:
// 普通then
.then(res=>{
console.log(res);
},error=>{
console.log(error)
})
//catch
.then(res=>{
console.log(res);
})
.catch(error=>{
console.log(error)
})
明顯第二個(gè)相對(duì)前一個(gè)會(huì)更加優(yōu)雅一點(diǎn)规丽。
我們?cè)賮?lái)看一段代碼:
// 普通then
.then(res=>{
throw Error("have exception")
console.log(res);
},error=>{
console.log(error)
})
// catch
.then(res=>{
throw Error("have exception")
}).catch(error=>{
console.log(error)
})
運(yùn)行上面的代碼我們很清楚的發(fā)現(xiàn)catch能捕獲到then的異常,但是then的reject回調(diào)里面并不能捕獲到resolve的異常段化。這在一定程度上保證了代碼的正常運(yùn)行順序嘁捷。
2.3、 finally
finally是es2018才引入的屬性显熏,不管 Promise 對(duì)象最后狀態(tài)如何雄嚣,都會(huì)執(zhí)行的操作。其本質(zhì)也是then方法的特性。
promise
.finally(() => {
// ...
});
// 等同于
promise
.then(
result => {
// ...
return result;
},
error => {
// ...
throw error;
}
);
2.4缓升、 all
Promise.all方法用于將多個(gè) Promise 實(shí)例鼓鲁,包裝成一個(gè)新的 Promise 實(shí)例。其基礎(chǔ)的語(yǔ)法是:
const p = Promise.all([p1, p2, p3]);
Promise.all方法接受一個(gè)數(shù)組作為參數(shù)港谊,p1骇吭、p2、p3都是 Promise 實(shí)例歧寺,如果不是燥狰,就會(huì)先調(diào)用下面講到的Promise.resolve方法,將參數(shù)轉(zhuǎn)為 Promise 實(shí)例斜筐,再進(jìn)一步處理龙致。
關(guān)于p的狀態(tài),主要由接收的promise數(shù)組決定的
(1)只有p1顷链、p2目代、p3的狀態(tài)都變成fulfilled,p的狀態(tài)才會(huì)變成fulfilled嗤练,此時(shí)p1榛了、p2、p3的返回值組成一個(gè)數(shù)組煞抬,傳遞給p的回調(diào)函數(shù)霜大。
(2)只要p1、p2此疹、p3之中有一個(gè)被rejected僧诚,p的狀態(tài)就變成rejected,此時(shí)第一個(gè)被reject的實(shí)例的返回值蝗碎,會(huì)傳遞給p的回調(diào)函數(shù)湖笨。
這里值得我們注意的一點(diǎn)是,如果p1,p2,p3中有錯(cuò)誤 并且有自己的catch方法的時(shí)候蹦骑。會(huì)調(diào)用其自己的catch慈省,當(dāng)沒(méi)有catch方法的時(shí)候才會(huì)拋給p的catch來(lái)處理
2.5、 race
Promise.race方法同樣是將多個(gè) Promise 實(shí)例眠菇,包裝成一個(gè)新的 Promise 實(shí)例边败。其基礎(chǔ)的語(yǔ)法是:
const p = Promise.race([p1, p2, p3]);
和all方法不同的是:
(1)只有p1、p2捎废、p3的狀態(tài)都變成rejected笑窜,p才會(huì)變成rejected
(2) 如果p1、p2登疗、p3中有一個(gè)狀態(tài)變成fulfilled排截,p的狀態(tài)就會(huì)變成fulfilled 并將首次變成fulfilled的返回值返回嫌蚤。
四、 初次手寫(xiě)的promise
前面大概都將所謂的promise的用法給描述了一遍断傲,大概寫(xiě)一兩個(gè)例子就能感受出promise的奧秘脱吱。那么如果讓你自己來(lái)寫(xiě)一個(gè)promise,你應(yīng)該如何來(lái)寫(xiě)呢认罩?
這是我下班后寫(xiě)的一個(gè)Promise:
const PENDING = "pending";
const RESOLVE = "resolve";
const REJECTED = "rejected";
function JPromise(fn){
const that = this;
that.state = PENDING;
that.value = null;
that.resolvedCallbacks = [];
that.rejectedCallbacks = [];
function resolve(value){
if(that.state === PENDING){
that.state = RESOLVE;
that.value = value;
that.resolvedCallbacks.map(cb=>cb(that.value));
}
}
function reject(value){
if(that.state === PENDING){
that.state = REJECTED;
that.value = value;
that.rejectedCallbacks.map(cb=>cb(that.value))
}
}
try{
fn(resolve,reject)
}catch(e){
reject(e)
}
}
JPromise.prototype.then = function(onFulfilled,onRejected){
onFulfilled = typeof onFulfilled === 'function'?onFulfilled:v=>v;
onRejected = typeof onRejected === 'function'?onRejected:r=>new Error(r);
if(this.state === PENDING){
this.resolvedCallbacks.push(onFulfilled)
this.rejectedCallbacks.push(onRejected)
}
if(this.state ===RESOLVE){
onFulfilled(this.value);
}
if(this.state === REJECTED){
onRejected(this.value)
}
}
然后運(yùn)行一下:
new JPromise((resolve,rejected)=>{
resolve(1)
}).then(res=>{
console.log(res);
},error=>{
console.log(error);
})
然后自測(cè)了一下箱蝠,完美能實(shí)現(xiàn)簡(jiǎn)單的promise。但是跟大佬交流之后才知道:原來(lái)promise有自己的一套標(biāo)準(zhǔn)垦垂,我這套代碼雖然能夠簡(jiǎn)單的實(shí)現(xiàn)promise的功能 但是沒(méi)有達(dá)到那套標(biāo)準(zhǔn)宦搬。
五、 符合規(guī)范的promise
首先關(guān)于promise規(guī)范乔外,我就簡(jiǎn)單的提一下吧:
- 每個(gè)then方法都返回一個(gè)新的Promise對(duì)象(原理的核心)
- 如果then方法中顯示地返回了一個(gè)Promise對(duì)象就以此對(duì)象為準(zhǔn)床三,返回它的結(jié)果
- 如果then方法中返回的是一個(gè)普通值(如Number、String等)就使用此值包裝成一個(gè)新的Promise對(duì)象返回杨幼。
- 如果then方法中沒(méi)有return語(yǔ)句,就視為返回一個(gè)用Undefined包裝的Promise對(duì)象
- 若then方法中出現(xiàn)異常聂渊,則調(diào)用失敗態(tài)方法(reject)跳轉(zhuǎn)到下一個(gè)then的onRejected
- 如果then方法沒(méi)有傳入任何回調(diào)差购,則繼續(xù)向下傳遞(值的傳遞特性)。
最后張貼一下 我改進(jìn)的代碼:
const PENDING = 'pending';//初始態(tài)
const FULFILLED = 'fulfilled';//初始態(tài)
const REJECTED = 'rejected';//初始態(tài)
function Promise(executor){
let self = this;//先緩存當(dāng)前promise實(shí)例
self.status = PENDING;//設(shè)置狀態(tài)
//定義存放成功的回調(diào)的數(shù)組
self.onResolvedCallbacks = [];
//定義存放失敗回調(diào)的數(shù)組
self.onRejectedCallbacks = [];
//當(dāng)調(diào)用此方法的時(shí)候汉嗽,如果promise狀態(tài)為pending,的話可以轉(zhuǎn)成成功態(tài),如果已經(jīng)是成功態(tài)或者失敗態(tài)了欲逃,則什么都不做
function resolve(value){ //2.1.1
if(value!=null &&value.then&&typeof value.then == 'function'){
return value.then(resolve,reject);
}
//如果是初始態(tài),則轉(zhuǎn)成成功態(tài)
//為什么要把它用setTimeout包起來(lái)
setTimeout(function(){
if(self.status == PENDING){
self.status = FULFILLED;
self.value = value;//成功后會(huì)得到一個(gè)值饼暑,這個(gè)值不能改
//調(diào)用所有成功的回調(diào)
self.onResolvedCallbacks.forEach(cb=>cb(self.value));
}
})
}
function reject(reason){ //2.1.2
setTimeout(function(){
//如果是初始態(tài)稳析,則轉(zhuǎn)成失敗態(tài)
if(self.status == PENDING){
self.status = REJECTED;
self.value = reason;//失敗的原因給了value
self.onRejectedCallbacks.forEach(cb=>cb(self.value));
}
});
}
try{
//因?yàn)榇撕瘮?shù)執(zhí)行可能會(huì)異常,所以需要捕獲弓叛,如果出錯(cuò)了彰居,需要用錯(cuò)誤 對(duì)象reject
executor(resolve,reject);
}catch(e){
//如果這函數(shù)執(zhí)行失敗了,則用失敗的原因reject這個(gè)promise
reject(e);
};
}
function resolvePromise(promise2,x,resolve,reject){
if(promise2 === x){
return reject(new TypeError('循環(huán)引用'));
}
let called = false;//promise2是否已經(jīng)resolve 或reject了
if(x instanceof Promise){
if(x.status == PENDING){
x.then(function(y){
resolvePromise(promise2,y,resolve,reject);
},reject);
}else{
x.then(resolve,reject);
}
//x是一個(gè)thenable對(duì)象或函數(shù)撰筷,只要有then方法的對(duì)象陈惰,
}else if(x!= null &&((typeof x=='object')||(typeof x == 'function'))){
//當(dāng)我們的promise和別的promise進(jìn)行交互,編寫(xiě)這段代碼的時(shí)候盡量的考慮兼容性毕籽,允許別人瞎寫(xiě)
try{
let then = x.then;
if(typeof then == 'function'){
//有些promise會(huì)同時(shí)執(zhí)行成功和失敗的回調(diào)
then.call(x,function(y){
//如果promise2已經(jīng)成功或失敗了抬闯,則不會(huì)再處理了
if(called)return;
called = true;
resolvePromise(promise2,y,resolve,reject)
},function(err){
if(called)return;
called = true;
reject(err);
});
}else{
//到此的話x不是一個(gè)thenable對(duì)象,那直接把它當(dāng)成值resolve promise2就可以了
resolve(x);
}
}catch(e){
if(called)return;
called = true;
reject(e);
}
}else{
//如果X是一個(gè)普通 的值关筒,則用x的值去resolve promise2
resolve(x);
}
}
//onFulfilled 是用來(lái)接收promise成功的值或者失敗的原因
Promise.prototype.then = function(onFulfilled,onRejected){
//如果成功和失敗的回調(diào)沒(méi)有傳溶握,則表示這個(gè)then沒(méi)有任何邏輯,只會(huì)把值往后拋
//2.2.1
onFulfilled = typeof onFulfilled == 'function'?onFulfilled:function(value){return value};
onRejected = typeof onRejected == 'function'?onRejected:reason=>{throw reason};
//如果當(dāng)前promise狀態(tài)已經(jīng)是成功態(tài)了蒸播,onFulfilled直接取值
let self = this;
let promise2;
if(self.status == FULFILLED){
return promise2 = new Promise(function(resolve,reject){
setTimeout(function(){
try{
let x =onFulfilled(self.value);
//如果獲取到了返回值x,會(huì)走解析promise的過(guò)程
resolvePromise(promise2,x,resolve,reject);
}catch(e){
//如果執(zhí)行成功的回調(diào)過(guò)程中出錯(cuò)了睡榆,用錯(cuò)誤原因把promise2 reject
reject(e);
}
})
});
}
if(self.status == REJECTED){
return promise2 = new Promise(function(resolve,reject){
setTimeout(function(){
try{
let x =onRejected(self.value);
resolvePromise(promise2,x,resolve,reject);
}catch(e){
reject(e);
}
})
});
}
if(self.status == PENDING){
return promise2 = new Promise(function(resolve,reject){
self.onResolvedCallbacks.push(function(){
try{
let x =onFulfilled(self.value);
//如果獲取到了返回值x,會(huì)走解析promise的過(guò)程
resolvePromise(promise2,x,resolve,reject);
}catch(e){
reject(e);
}
});
self.onRejectedCallbacks.push(function(){
try{
let x =onRejected(self.value);
resolvePromise(promise2,x,resolve,reject);
}catch(e){
reject(e);
}
});
});
}
}
具體的代碼細(xì)節(jié)呢?我在代碼中已經(jīng)做了標(biāo)注,如果又不懂的可以私聊肉微、微信 歡迎來(lái)擾匾鸥。
說(shuō)在最后
已經(jīng)有好幾個(gè)中午好怎么睡午覺(jué)了,這篇文章寫(xiě)的有點(diǎn)敷衍碉纳。本來(lái)下班的時(shí)候就8點(diǎn)半了勿负,然后又整理/調(diào)試代碼 整了半天,結(jié)果在寫(xiě)文章的時(shí)候想例子想個(gè)半天都想不出來(lái)劳曹。我明天會(huì)利用休息時(shí)間好好來(lái)修改一下這篇文章的奴愉,太困了 先這樣吧,我洗澡去睡覺(jué)了铁孵。您如果對(duì)現(xiàn)在的這篇文章不太滿意锭硼,就請(qǐng)過(guò)一天再來(lái)看。
最后提一句蜕劝,能不能給我漲點(diǎn)人氣啊檀头,寫(xiě)了一年多,還是這點(diǎn)人氣..