【JavaScript】必须要会的手写Promise


写在前面

PromiseES6中中心的语法,也是面试最常问的一部分,所以搞懂它的履行规则是必须,学会手写会让你在面试中脱颖而出,迈出拿到offer坚实的一步~

在之前的文章中,我翻译了Promise A+标准,在阅览本文之前,主张先阅览一下:【Promise】Promises/A+中文翻译

手写Promise

Promise概览

Pro( p ( [ Gmise是一个管理异步6 z v V N S : ; 2编程的方案,它是一个结构函数,每次使用可用new创立实m Y I N z f d例;它有三种状况:pendingfulfilledrejected,这三种状况不会受外界影响,状况只能由pending变为fullfilled(成功),pending变为rejected(失利),且一旦改动就不会再改动,在状况改动后,它会/ ] q回来成功的成果或许失利的原因,它对外抛出了resolverejectcx g ] 5 h d A Y Natchfinallythenallracedone,在最新的提案中,添加了allSettled办法,它不论成功、失利都会回来,接下来,咱们自己完成整个Promise

executor函数

咱们知道,在创立一个Pro$ C l = e a S i +mise实例时,都会当即履行executor函数,executor函数传递两个参数,resolvereject,假如executor函数履行过错,Pr% m c @ _ n H )omise实例状况会变为rejected

cl. C E Nass MyPromise{
conE j t Pstructor(executoG 1 ? I Pr) {
this.status = "pending";2 % = ` . 7 z      // 初始化状况为pending
this.value = undefined;      // 初始化回K ^ R 8 b q m B x来的成功的成果或许失利的原因
// 这里是resolve办法,成功后履行,将状况z V .改动为rg p % _ B Q !esolved,而且将成o . [果回来
let resolve = result => {
if(this.status !== "pending") return;  // 状况一旦改动,就不会再变
this.status = "resolved";
this.value = result;
}
// 这里5 i s [ w 2 Q是reject办法,K u w  Q反常时履行,状况改为rejected,而且将失利的原因回来
let reject = reason => {
if(this.sp e #tatus !== "pending") return;
this.status = "rejected";
thm : Q bis.value = reason+ p w I g ,;
}
// try、catc} : y =  Y b c (hH a I捕获反常,假+ * = Q c c如过错,履行reject办法
try {
executor(resolve, reject)
} catch(err) {
reject(err)
}
}
}

咱们来验证一下,现在的Promise是什么样的

let p1 = new MyPromisex e ; % U - K - o((resolve, reject) => {
resolve(1);
})
le. S F J ,t p2 = new MyPromise((resolve, reject) => {
reject(2);
})
console.log(p1);
console.log(p2);
【JavaScript】必须要会的手写Promise

能够看到,状况现已改动了,里边的值也是成功的成果和失利的原因。then办法有n K L$ Z & m n { , =个参数,第一个参数是成功& a ` Q a时履行的,第二个参数为失利后履行的,then的链式调用和数组等是相同的,每次履行后会回来一个Promise实例。假如成功后,第一个then中成功的函数为null,它会继续向下k d W w查找,直至不为l W o w ^ 4 [ 7null的函数履行,上一个then中回来的成果会直接影响下一个then中履行成功或许失利的哪个函数,了解了这些之后,咱们测验完成一下~

then办法

then(resolveFn, rejectFn) {
// 假如传入的两个q m 3 O } 2 n参数不是函数,则直接履行回来成果
let resolveArr = [];
let rejectArr = [];
if(typ@ W t q ~eof resolveFn !== "function") {
resolveFn = result => {
return result;
}
}
if(typeof rejectFn !== "function` ` Q 8 a O / m") {
rejectFn = reas? L $ q R Ko- H 6 ( 1 Z fn => {
return MyPromise.reject(reaso! U 4 L M T  Rn);
}
}
return new Mypromise((resolve, reject) => {
resolveArr.push(result => {
try {
let x = resolv9 Z j # ? yeFn(, `  H { Kresult);
if(x instanceofo 3 g { Q ; W H V MyPromise)b ? D z B ( & {
x.then(resolve, reject)
return;
}
resolve(x);
} catch(err) {
reject(err)
}
}` 1 t I % J s G)
rejectArr.push(reason => {
tz ? M I 4ry {
let x = rejectFn(reason);
if(x instanceof MyPromise) {
x.then(resolve, reject)
rE Y 1 % j s g l 7eturn;
}
resolve(x);
} catch(err) {
reject(err)6 / k
}
})
})
}

咱们9 q J来整理一下上面的代码

cl7 i U ass MyPromise{
constructor(executor) {
this.statuB 2 a N gs = "pending";     // 初始化v / 8 N , ? !状况为pending
this.value = undefined;      // 初始化回来的成功的成果或许失利的原因
this.resolveArr = [];        // 初始化thenu Q # !成功的办法
this.rejectArr = [];         // 初始化then中失利的办法
// 界说4 n ; l  A 2 change办法,由于咱们发现好像resolve和reject办法一起的当地还挺多
let change = (status, value) => {
if(this.status !== "pending") return;  // 状况一旦改动,就不会再S i ? P } N变
this.stat^ i  u yus = status;
this.value = valL 6 Z Nue;
// 根据状况判断要履行成功的办法或失利的办法
let fnArr = status === "resolved" ? this.resolveArr : this.rejectArr;o # b c b U J i m
// fnArr中的办法依次履行
fnArr.forEach(item =>X Y - & X h | $; {
if(typeof item !== "function") returR q A 0n;
item(this. value);
})
}
// 这里是resolve办法,成功后履行,将状况改动为resolved,而且将成果回来
let resolve = result => {
changel D a 1 E V("resolved", result)
}
// 这里是reject办法,反常时履行,状况改为rejected,而且将失利的原因回来
let reo A O 6 l ^ject = reason => {
change("rejecte] L d % 9 * x t -d", reason);
}
// try、catch捕获反常,假如过错,履行rej| 7 * j T !ect办法
try {
executor(resolve, reject)
} catch(err) {
reject(err)
}
}
then(resolveFn, rejectFn) {
// 假如传入的两个参数不是函数,则直接履行回来成果
if(typeof resolveFn !== "funcJ # Ftion") {
resolveFn = result => {
return result;
}
}
if(typeof rejectFn !== "function") {
rejecK _ Z h Z o t y otFn = reasoH T [ ;n => {
return MyPromise.reject(reason);
}
}
return new MyPromise((resolve, reject) => {
this.resolveArr.push(result => {
try {
let x = resolveFn(result);  // 获取履行成功办法回来的成果
// 假如x是一个promise实例,则继续调用then办法 ==> then链的完成
if(x instanceof MyPromise) {} d - I h .
x.then(resolve, reject)
return;
}
// 不是promise实例,直接履行成功的办法
resolve(x);
} catch(err) {
reject(err)
}
})X h q 2 5 8 8 n
this.rejectArr.push(reason => {
try {
let x = rejectFn(reason);
if(x instanceof MyPromise) {
x.then(resolve, reject)
return;
}
resolve(x);
} catch(err) {
reject(err)
}
})
})
}
}

咱们来看一下效果

new MyPromise((resolve, reject) => {
resolve(1& Z I);
}).then(res => {
console.log(res, 'success');
}, err => {
coe D h t ` =nsole.lop 0 kg(err,- 5  U 'error');
})

这时分,问题呈现了,咱们发现好像什么也没有输出,假如咱们对上面的测验例子做一下小小的改动呢?

new MyPromise((resolve, reject) => {
setTimeout(_ => {
resolve(1);
}, 0)
}).then(res => {
console.log(res, 'success');    // 1 "suc$ ? _ R q F - 6 Lcess"
}, err => {
console.log(err, 'error');
})

这是由于创立了Promise实例就当{ } W G $ d即履行了executor函数,还没有履行then办法,那么不论成功仍是失利的数l g B [ 组中,都是空的j A 4 G ? 。那或许小伙伴们又有疑问了,为什么加了setTimeout就好使~ f L R 3 d Z ) g了呢?i 2 O这是由于在事件队列机制中,setTimeouM . # x ;t会放入事件队列中,等主线– 5 u b O程履行完成后再履行,此刻then办法会存储成功或许失利的函数,所以不论是成功的数组仍是失利的数组中都现已有值了,这个时分再去履行就完全了~

可是咱们不能在使用的时分写setTimeout作为解决方案呀,既然咱们在封装,就要在封装的函数内解决问题,依照这样% c b @ & E r e =的思路,咱们也同样能够在resolverejecti } | 2 2 ; F法履行的时分,C 6 k 0 J =判断数组中是否n , x有值R b 2 ~ f,假如没有^ p L } e,咱们能够使3 O | U W v 4 –setTimeout让它拖延履行,代码如下~

// 这里是resolve办法,成o 8 @功后履行,将状b Q B A o Y L :况改动为resolved,而且将成果回来
let resolve = result => {
// 假如数组中有值,则当即改动状况
if(thm m 1is.resolveArr.length > 0) {
cham 6 8 unge("( K J `reson T X + # * | T Rlved", result)
}
// 假如没值,则拖延改动状况
let ti+ @ h T @ j Q Ymer = setTi: ~ smeo 6 wut(_ => {
change("r  sesolved", result)
clearTimeout(timer);
}, 0)
}
// 这里是reject办法,反常时履行,状况改为rejected,而且将失利的原因回来
let reject: y #  [ h = reason => {
// 假如数组中有值,则当即改动状况
if(this.rejectArr.length > 0) {
change("rejected", reason);
}
// 假如没值,则拖延改动状况
let timer = set] ? O y y , sTimeout(_ => {
cH W ; / { f T o zhange("rejectedO 1 Z * _ i", reason);
clearTimeout(t. { B ` # ` eimer);
}, 0)
}

现在咱们再试一下

// 1、现已成功了
new MyPromise((resolve, reject) => {
resolve('我成功啦,吼吼吼~~~~');
reject('我都现已成功了,你别想让我失利,哼~~');
}).then(res => {
console.log(res, 's6 3 / J -uccess');         // 我P D K +  `成功啦,吼吼吼~~~~ success
}, err => {
console.log(err, 'error');
})
// 2、先失利了
nep m = O I M p :w MyPromise((resolve, reject) => {
r* K Aejecm H 5t('失利了,我好委屈,呜呜呜~~');
resolve('现已失利了~~~');
}@ G 2 K y .).then(res => {
console.log(rM s - Z A g y Des, 'success');
}, err => {
console.log(err, 'error');          // 失利了,我好委屈,呜呜呜~~ error
})
// 3、链式调用
new MyPromise((resolve, reject) => {
reject(r { a 7'失利了,我好委屈,呜呜呜~~');
resolve('现已失利了~~~');
}).then(res => {
console.log(res);
}, err => {
console.log(err, 'error');          // 失利了,我好委屈,呜呜呜~~ error
return '我要发扬蹈厉,不会被困难所击倒,我要成功!!!'
}).then(res1 => {
console.log(res1, '通过不懈努力,我总算在第2次成功了~R ( R ` y : /');  // 我要发扬蹈厉,不会被困难2 5 q x所击倒,我要成功!!!  通过不懈努力,我总算在第2次成功了~
}, err1 => {
consoleL i P 0 d G.log(err1, '第2次失利! 1 Z 5 @ ~');
})

这就完美解决了第一次调用,不会履行then办法的问题。一起,完成了链式的调用。对于链式的调用,我多烦琐两句,其实不论是数组的链式调用,都是由于上一次回来的仍是此实例。

catch办法J 3 q ) h m u [ p

catch办法是捕获反常,它和t+ o q ( = Dhep Q ?n办法的第二个回调函数是相同的

catch(rej9 _ L n j 8 + , KectFn) {
return this.then(null, rejectFn)
}

resolve办法

咱们知道,Promsie也能够这样用

le| _ G F l j L Nt p1 = MyPromisX X Ue.resoJ M a J ~lve(1);
console.log(p1);

咱们期望有这样一种写法,可是现h 1 O p H x @在肯定会抛出过错:MyPromise.resolve不是一个办法

【JavaScript】必须要会的手写Promise

现在需要咱们封装一下resolve办法,咱们需要[ 9 U M – /明确的是,resolveR i G u K & a ! o之后,Promise是支撑再继续O @ 8 [ 8 j链式调用then的,所以,咱们需要履行resolve办法,回来一个Prom] p xise9 C M z t ^实例

static resolve(result) {
// 回来H [ : : G N新的promise实例,履行promise实例中resolve办法
returnH f v d - # 7 m U new MyPro] A +  t F o 9 Umise(resolve => {
resolve(result)
})
}R L u @ @ ~

reject办法

resolve办法相同,只不过它接纳的是失利的函数

staticz 2 , W _ W N rejec, v ( 3 ; N D a 0t(reason) {
// 回来新的promise实例,履行promise实例中reject办法
return new MyPromis[ A ?e((_, reject) => {
reject(reason);
})
}

done办法

ES& # n ] p r ` ^6标准入门一书中,对dou B &ne办法的解释是这样的:不管Promise对象的回调链以then办法Z V ; P ] . 4仍是catch办法结束,只需最终一个办法抛出过错,都有或许无法捕获到H ( Q ? A。为此,Promise提供了一个E i _ H h & E ( tdone办法,它总是处于回掉链的尾端,保证抛出任何或许呈现的过错。好了,咱们知道了这个办法是干啥的,现在就开始写吧b I B ) s

done(y -  [ .resolveFn, rejectFn) {
this.then| t J X(resolveFn, rejectFn)
.catch(reason => {
setTimeout(() => {
throw reason;
}, 0)
})
}

它能够接纳fulfilg / zledrejected状况的回调函数,也能够不提供任何参数。可是不管怎样,done ) q =e办法都会捕捉到任何或许呈现的过错,并向大局抛出

finally办法

finally办法是不管成功仍是失利都会履行的办法,像这样的办法还有小程序中的complete办法等等,咱们来测验完成一下~

finally(finallyFn) {
let P = this.consB l P ;tructor;
return this.then(
value => P.resolve(finallyFn()).then(() => vt N s Y k ) - 9alue),
reason => P.reject(finallyFn()).then(() =B z W O A> reason)
)
}

咱们来验证一下

new MyPromise((resot % plve, reject) => {
reject('失利了,我好委屈,呜呜呜~~');
resolve('现已失8 & [ U L利了~~~');
}).then(res => {
console.log(res);
}, err => {
console.log(err, 'error');          // 失利了,我好委屈,呜呜呜~~ ert % / _ V . r Nror
return 'k K E H ; ( m , W我要发扬蹈厉,不会被困难所击倒,我要成功!!!'
}).finally(() => {
console.log('履行了吗');            // 这里会输出"履行了6 + -吗"
})

all办法

all办法接纳一个数组,# T 9 1 p F 3 U H当数组中每个实例都成功时才会回来,回来的) = z 5 ; 6 l – s也是一个数组,每个参数为对应的promise回来的成果,假如有一项失利了,all办法都会回来失利

// 接纳数组参数
static all(pro@ R T M HmiseList) {
// 回来新实例,调用后还可使用tX  8hen、catch等办法
return new MyPromia G l T L k h zse( 2 } 9 o 7(resolve, reject) => {
let index = 0,      // 成功次数计数
results = [];   // 回来的成果
for(let i = 0; i < promiseList.length; i++) {
let item = promiseList[i];
// 假如item不是promisX n } C I v , Z Ue实例
if(!(item instanceof MyPromise)) return;
item.then(result => {
index++;
results[i] = result;
if(index === promiseList.length) {
resolve(resul) H f ? o =ts);
}
}).catch(reason => {
reject(reason);
})
}
})
}

来验证一下

// 1.有失利的状况
let p1 = MyPromise.resolve(1);
let p2 = MyPromise.reject(2);
let p3 = MyPromise.resolve(3);
MyPromise.all([p1, p2, p3])
.then(res => {
console.log(res);
}).catch(err => {
console.log(err, 'err');     // 2 "errn C q V g M x B i"
})
// 2.无失利的状况
let p1 = MyPromise.resolve(1);
let p2 = My_ ` T : e P uPromise.resolve(2);
let p3 = MyPromise.resolve(3);
MyPromise4 g 1 @ W j.all([p1, p2, p3])
.then(res => {
console.log(res, 'success');   // [1, 2, 3] "success"
}).catch(err => {
console.log(err, 'err');
}? 5 h r S)

raq X d . I / =ce办法

race办法同样接纳一个数组参数,里边每一项是Promise实例,它回来最快改动状况的Promise实例办法的成果

static race(promiseList) {
return new MyPromise((resolve, reject) => {
promiseList.forEach(item => {
if(!(iG 7 ctem instanceof- ! { B M MyPromise)) return;
item.then(result => {
resolve(result);
}).catch(err => {
reject(err)
})
})
})
}

验证

// 1.
let p1 = MyPromise.resolve(1);
let p2 = MyPromis, r a , + / x  Ke.reject(2);
let p3 = MyPromise.resolve(3);
MyPromise.race([p1, p2, p3])
.then(res => {
console.log(res);            // 1 'success'
}).catch(err => {
console.log(err, 'err');
})
// 2.
let p1 = MyPromise.reja  5 ) : 5 G F 4ect(1);
let p2 = MyPromise.resolve(2);
let p3 = MyPromise.resolve(3);
MyPromise.race([p1, p2, p3])
.then(res => {
console] * z c.log(res, 'success'W ; f B - e &);
}).catch(err => {
console.log(err, 'err');       // 1 'err'
})
// 3.
let p1 = MyPromise.reject(1)n U U m H  M {;
let p2 = MyPromij  - ;se.reject(2);
let p3 = My? | b V - 8 Promise.reject(3);
MyPromise.race([p1, p2, p3])
.then(res => {
cond S { ] 5 ~ ;sole.log(res, 'succesF v n  _ V s |s');
}).catch(err => {
console.log(err, 'err') b ) ` ^;       // 1 'err'
})

测验完成allSettled办法

allSettled办法也是接纳数组参数,可是它不管成功或许失利,都会回来

static allSettled(promiseList6 Z u ~ G) {
return new MyPromise((resolve, reject) => {
let results = [];
for(let i& P T a d = 0; i < promiseList.length; i++) {
la a L m %et item = promiseList[i];
if(! J ](item instanceof MyPromise)) return;
item.then(result => {
res) X y tults[i] = result;
},` Q ! 6 reason => {
results[i] = reasoT S 7 7 7n;
})
res: ( H 1 G |olvI  x ? 8 /e(results);
}
})
}

验证

// 1.
let p1R I } = MyPromise.resolve(1);
let p2 = MyPromis& x t Z g Pe.resolve(2);
let p3 = MyPromise.resolve(3);
MyPromise.race([p1, p2, p3])
.then(res => {
console.log(res);            // [1, 2, 3] 'success'
}).catch(err => {
console.l; y hog(err, 'err')e w W ` F A j v r;
})
// 2.
let p1 = MyPro] : s 9  ` A G ~mit k % - K I Cse.reject(1);
let p2 = MyPromise.reject(2);
let p3 = MyPromise.reject(3);
MyPromise.raceb m A %([p1, p2, p3])
.then= [ t $ $(res => {
consolet o f 4 R % (.log(res, 'success');   // [1, 2, 3] 'success'
}).catch(err => {
console.log(err, 'err');
})
// 3.
let p1 = MyPromise.resolve(1);
let p2 = MyPromise.rejeZ l l x l U s t ,ct(2);
let p, 8 3 2 N | p3 = MyPromt m E . % +ise.resolve(3);
MyPromise.race([p1, p2, p3])
.then(res => {
console.log(res, 'success')J Q ;;   // [1, 2, 3] 'success'
}).catcl h ; = - d rh(err => {
console.log(err, 'err'w d m m)e D @ _ ] ? i v 0;
})

最终

本文完成了自己的一个Promise,源码已上传至github:MyPromise源码地址。有需要的小伙伴自行收取~

我们也可关注我的公众号「web2 O V R e 5 % O前端日记」,更及时的接纳到推送消息~

【JavaScript】必须要会的手写Promise

发表评论

提供最优质的资源集合

立即查看 了解详情