likes
comments
collection
share

【promise】—— 让一家子逃离回调地狱

作者站长头像
站长
· 阅读数 18

原来,在JS的发展过程中,程序员为了解决异步带来的执行顺序问题,竟也演变出这样一段绞尽脑汁逃离回调地狱的可歌可泣的发展史,今日得窥真相,很难不与之共情,为之动容。

儿子比爸爸先生?

现在有这样一段代码,描绘了小明一家的温馨家族史。从代码的执行顺序来看,按照我们的预想,先是爸爸出生,然后再有的小明,但 V8 的执行结果真的是这样吗?

function father() {
    setTimeout(() => {
        console.log('爸爸出生了');
    }, 1000)
}

function son() {
    console.log('儿子出生了');
}

father()
son()

惊世骇俗,明明输出 “爸爸出生了” 的执行函数(foo)在前,可 V8 的执行结果却是儿子先出生,这是怎么一回事?

【promise】—— 让一家子逃离回调地狱

异步

原来,这就是 JS 的一种执行机制,异步:

  • js是单线程执行的,一次只能干一件事。
  • 遇到需要耗时的代码,那就先挂起,先执行不耗时的代码,等到不耗时的代码执行完了,V8 腾出手了,再来执行耗时代码。

正是因为异步的执行机制存在,虽然foo函数写在bar函数前面,但耗时长的foo函数,放在了bar函数之后执行,才上演了一场爸爸在儿子之后出生的闹剧。

回调

那个时候还没有官方的办法来解决这个问题,但是,为了避免这类情况,程序员们智慧的结晶——回调,应运而生。

所谓的回调,就是在原本的代码上进行修改,将 bar() 函数,放在 foo() 函数的内部调用。

function father() {
    setTimeout(() => {
        console.log('爸爸出生了');
        son()     // 将 bar() 函数,放在 foo() 函数的内部调用
    }, 1000)
}

function son() {
    console.log('儿子出生了');
}

father()

【promise】—— 让一家子逃离回调地狱

不错,这下父子关系是理清楚了,但回调这种方法治标不治本。试想,如果在这样一份成百上千的嵌套关系中,你想在某一处修改一些功能,不修则已,一修便是牵一发而动全身,这样绝望的场景当初用了一个名词来形容——回调地狱

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()的返回值,确保调用链的正确执行。

【promise】—— 让一家子逃离回调地狱

补充:再度细分:race() 与 all()

race() —— 爸爸和爷爷的赛跑

细心的友友就要问了,如果说这些执行函数的执行时间不同,我想让其中一些函数中执行快的先执行,那么该怎么办呢?

正好,爷爷和爸爸想比试一场,想看看到底是谁先跑回家,但可以看出爷爷终究还是敌不过爸爸年轻的优势,运行时间比爸爸慢了不少。

【promise】—— 让一家子逃离回调地狱 【promise】—— 让一家子逃离回调地狱

至于儿子,虽然他年轻力壮的,是跑最快的一个,但有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()
})

【promise】—— 让一家子逃离回调地狱

all() —— 孝顺的儿子

all()方法与race()方法唯一不同的一点在于,其要等all()中所有函数执行完毕后,再执行.then()内的函数,按上个例子来说,儿子就得等爷爷和爸爸都跑到家了,他才能跑到家。

同时调用 grandpa()father() 这两个函数,等两个函数都执行完毕后,再调用 son() 函数。

Promise.all([grandpa(), father()]).then(() => {
    son()
})

【promise】—— 让一家子逃离回调地狱

可以看出使用all()后的son()显得异常孝顺,硬是等待grandpa()father()执行完毕再执行。

最后

至此,promise终于完成了它解决异步带来的执行顺序问题的使命,正确排好了这一家子的先后顺序关系,真是可喜可贺。

转载自:https://juejin.cn/post/7398511534736621602
评论
请登录