【promise】—— 让一家子逃离回调地狱
原来,在JS的发展过程中,程序员为了解决异步带来的执行顺序问题,竟也演变出这样一段绞尽脑汁逃离回调地狱的可歌可泣的发展史,今日得窥真相,很难不与之共情,为之动容。
儿子比爸爸先生?
现在有这样一段代码,描绘了小明一家的温馨家族史。从代码的执行顺序来看,按照我们的预想,先是爸爸出生,然后再有的小明,但 V8 的执行结果真的是这样吗?
function father() {
setTimeout(() => {
console.log('爸爸出生了');
}, 1000)
}
function son() {
console.log('儿子出生了');
}
father()
son()
惊世骇俗,明明输出 “爸爸出生了” 的执行函数(foo
)在前,可 V8 的执行结果却是儿子先出生,这是怎么一回事?
异步
原来,这就是 JS 的一种执行机制,异步:
- js是单线程执行的,一次只能干一件事。
- 遇到需要耗时的代码,那就先挂起,先执行不耗时的代码,等到不耗时的代码执行完了,V8 腾出手了,再来执行耗时代码。
正是因为异步的执行机制存在,虽然foo函数写在bar函数前面,但耗时长的foo函数,放在了bar函数之后执行,才上演了一场爸爸在儿子之后出生的闹剧。
回调
那个时候还没有官方的办法来解决这个问题,但是,为了避免这类情况,程序员们智慧的结晶——回调,应运而生。
所谓的回调,就是在原本的代码上进行修改,将 bar()
函数,放在 foo()
函数的内部调用。
function father() {
setTimeout(() => {
console.log('爸爸出生了');
son() // 将 bar() 函数,放在 foo() 函数的内部调用
}, 1000)
}
function son() {
console.log('儿子出生了');
}
father()
不错,这下父子关系是理清楚了,但回调这种方法治标不治本。试想,如果在这样一份成百上千的嵌套关系中,你想在某一处修改一些功能,不修则已,一修便是牵一发而动全身,这样绝望的场景当初用了一个名词来形容——回调地狱。
function a() {
b()
}
function b() {
c()
}
function c() {
d()
}
function d() {
e()
}
function e() {}
。。。。。。。。。。。。。
。。。。。。。。。。。。。 // 妄想修改某一处,牵一发而动全身
于是,promise
迎来了属于它的时代!
promise
2015年,es6提出了一个重要的语法:promise
。
promise
是一种解决方案 — 通过编写同步的代码的方式,来处理异步的一种解决方案,用来解决多层回调嵌套,可以把一个多层嵌套的同步、异步都有回调的方法给拉直为一串.then()
组成的调用链。
promise
问世之后,我们的代码就可以写成这样。
function father() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('爸爸出生了');
resolve();
}, 1000)
})
}
function son() {
console.log('儿子出生了');
}
father().then(() => { // father执行完后son再执行
son()
})
不仅如此,当我们不仅仅满足于家族中爸爸儿子的顺序关系,想再加上爷爷时,仅仅只需要在原来的基础上添加相应的grandpa()
函数,并用.then()
搭建好正确的调用链便是。
function grandpa() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('爷爷出生了');
resolve();
}, 1000)
})
}
function father() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('爸爸出生了');
resolve();
}, 1500)
})
}
function son() {
console.log('儿子出生了');
}
grandpa()
.then(() => { // grandpa执行完后father再执行
return father();
})
.then(() => { // father执行完后son再执行
son()
})
需要注意的一点是,在.then()
调用链的使用中需要手动添加例如return father()
的返回值,确保调用链的正确执行。
补充:再度细分:race() 与 all()
race() —— 爸爸和爷爷的赛跑
细心的友友就要问了,如果说这些执行函数的执行时间不同,我想让其中一些函数中执行快的先执行,那么该怎么办呢?
正好,爷爷和爸爸想比试一场,想看看到底是谁先跑回家,但可以看出爷爷终究还是敌不过爸爸年轻的优势,运行时间比爸爸慢了不少。
至于儿子,虽然他年轻力壮的,是跑最快的一个,但有race()
这个函数在前,他就不能露这个头,只能跟在race()
的获胜者后边。
等待 grandpa()
和 father()
中任何一个函数先执行完毕(无论成功还是失败),然后执行 son()
函数:
function grandpa() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('爷爷到了');
resolve();
}, 1500)
})
}
function father() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('爸爸到了');
resolve();
}, 1000)
})
}
function son() {
console.log('儿子到了');
}
Promise.race([grandpa(), father()]).then(() => {
son()
})
all() —— 孝顺的儿子
all()
方法与race()
方法唯一不同的一点在于,其要等all()
中所有函数执行完毕后,再执行.then()
内的函数,按上个例子来说,儿子就得等爷爷和爸爸都跑到家了,他才能跑到家。
同时调用 grandpa()
和 father()
这两个函数,等两个函数都执行完毕后,再调用 son()
函数。
Promise.all([grandpa(), father()]).then(() => {
son()
})
可以看出使用all()
后的son()
显得异常孝顺,硬是等待grandpa()
和father()
执行完毕再执行。
最后
至此,promise
终于完成了它解决异步带来的执行顺序问题的使命,正确排好了这一家子的先后顺序关系,真是可喜可贺。
转载自:https://juejin.cn/post/7398511534736621602