likes
comments
collection
share

踏入异步之潮——探索JS Promise的奥秘

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

1、引言

在Web开发中,许多操作如网络请求、文件读写、定时器等都是耗时操作,如果使用同步执行模式,这些操作将会阻塞主线程,导致用户界面无法响应,也就是常说的“页面卡死”。因此,异步编程在JavaScript中不仅是必要的,而且是至关重要的,而Promise的出现让让我们能够以链式调用的方式组织代码,使复杂的异步逻辑也能如丝般顺滑。

2、什么是异步

我们先来看一份简单的代码,foo()设置一个1秒的定时器模拟网络请求需要的时间:

function foo() {
    setTimeout(() => {
        console.log('我的阿勒泰');
    },1000)
}

function bar() {
    console.log('好好看');
}

foo()
bar()  

按理来说先执行foo()再执行bar(),应该会先打印“我的阿勒泰”,再打印“好好看”,但实际结果是

踏入异步之潮——探索JS Promise的奥秘

这是因为js是单线程的,一次只能执行一个任务;如果遇到需要耗时的任务,那就先跳过,先执行不耗时的代码,等到不耗时的代码执行完了,v8腾出手了,再执行耗时的代码,这就是异步。

与之相反的同步就是代码从上往下一步一步执行,谁也不跳过。这样就会大大降低代码的执行效率,所以异步编程是十分必要的。

3、Promise

3.1 情景引入

现在我们用两个函数模拟张三同志相亲结婚的过程

function xq() {
    setTimeout(() => {
        console.log('张三相亲了');
    },2000)
}

function marry() {
    setTimeout(() => {
        console.log('张三结婚了');
    },1000)
}

xq()
marry()

打印结果:

踏入异步之潮——探索JS Promise的奥秘

这显然不符合我们的预期,怎么能先结婚再相亲呢?为了张三能有个正常的成家过程,我们就要用到promise的解决方案!

3.2 promise应用

3.2.1 基础语法

①先在定义xq函数,当中return一个新promise对象。Promise里接收一个回调函数,函数参数为resolve和reject,分别代表执行状态成功和执行状态失败。这里就让张三相亲成功,通过调用resolve()方法通知Promise该异步操作已完成且成功

然后同理定义marry函数;

接着调用xq()并链式调用.then(),我们将marry()函数放在.then()中,意味着只有当相亲成功结束后,才会安排结婚。这样确保了事件按照预期的顺序发生,即使两个操作本质上都是异步的

function xq() {
    return new Promise((resolve,reject) => {
        setTimeout(() => {
            console.log('张三相亲了');
            resolve()
        },1000)
    })
}

function marry() {
    setTimeout(() => {
        console.log('张三结婚了');
    },1000)
}

xq().then(() => {
    marry()
})

这样就能按照我们预想的过程那样调用函数了:

踏入异步之潮——探索JS Promise的奥秘

②张三结完婚了,他还想要一个小baby,我们就再添加一个baby函数,同时为了执行完marry后再执行baby,同样应该在marry函数中返回一个promise对象:

function xq() {
    return new Promise((resolve,reject) => {
        setTimeout(() => {
            console.log('张三相亲了');
            resolve()
        },1000)
    })
}

function marry() {
    return new Promise((resolve,reject) => {
        setTimeout(() => {
        console.log('张三结婚了');
        resolve()
    },1000)
    })
}

function baby() {
    console.log('小张三出生了');
}

xq().then(() => {
    marry().then(() => {
        baby()
    })
})

这三个函数的调用代码还不够优雅,再优化一下,方便后续追加更多的函数,张三的二胎三胎更美丽~

xq()
.then(() => {
    return marry()
})
.then(() => {
    baby()
})

踏入异步之潮——探索JS Promise的奥秘

3.2.2 resolve传参

向resolve函数传递一个字符串作为参数值(在这个例子中是'相亲成功''新婚快乐!'),被后续的.then()接收,充当回调函数的参数resres2打印出来

function xq() {
    return new Promise((resolve,reject) => {
        setTimeout(() => {
            console.log('张三相亲了');
            resolve('相亲成功')     // 成功
        },1000)
    })
}

function marry() {
    return new Promise((resolve,reject) => {
        setTimeout(() => {
        console.log('张三结婚了');
        resolve('新婚快乐!')
    },1000)
    })
}

function baby() {
    console.log('小张三出生了');
}

xq()
.then((res) => {
    console.log(res);
    return marry()
})
.then((res2) => {
    console.log(res2);
    baby()
})

踏入异步之潮——探索JS Promise的奥秘

3.2.3 reject

异步处理操作成功(resolve)通过.then来注册回调,失败则使用.catch。 当 reject 被调用时,Promise的状态会变为失败,并且传递给它的参数(在这个例子中是字符串 'a')会被传递给 .catch 中的回调函数的参数err,并被打印出来

function a() {
    return new Promise(function(resolve, reject) {
        setTimeout(function() {
            console.log('a is ok');
            reject('a')
        }, 1000)
    })
}

function b() {
    console.log('b is ok');
}


a().then((res) => {
    b()
})
.catch((err) => {
    console.log(err);
})

踏入异步之潮——探索JS Promise的奥秘

补充:a().then((res) => { b() })也可以写成a().then(b)

小总结:

  1. then((res) => {}) res是promise中resolve(xx)出来的值
  2. catch((err) => {}) err是promise中reject(xx)出来的值

3.3 Promise的其他方法

3.3.1 Promise.race()

Promise.race([a(), b()]) 接收一个Promise数组作为参数,“赛跑”决定出最快的那个函数先执行(不论是resolve还是reject)。在这个例子中,由于b函数比a函数更快完成(500ms对比1000ms),所以race几乎立刻在b解决后继续执行,调用.then内的回调,进而执行c()函数。因此,你会看到“b is ok”先于“a is ok”打印,紧接着是“c is ok”。

function a() {
    return new Promise(function(resolve, reject) {
        setTimeout(function() {
            console.log('a is ok');
            resolve('a')
        }, 1000)
    })
}

function b() {
    return new Promise(function(resolve, reject) {
        setTimeout(function() {
            console.log('b is ok');
           // resolve('b')
           resolve('b')
        }, 500)
    })
}

function c() {
    console.log('c is ok');
}

Promise.race([a(), b()]).then(() => {
    c()
})

踏入异步之潮——探索JS Promise的奥秘

3.3.2 Promise.all()

Promise.all([a(), b()]) 会等待所有Promise都解决(或遇到第一个reject则提前结束),然后以一个包含所有Promise resolve值的数组形式解决。这意味着只有当a和b都完成之后,才会执行.then中的回调,打印“c is ok”。与race不同,它关注的是所有操作的完成而非速度

Promise.all([a(), b()]).then(() => {
    c()
})

踏入异步之潮——探索JS Promise的奥秘

3.3.3 Promise.any()

Promise.any([a(), b()]) 只要其中任何一个Promise解决(非reject状态),就立即以该Promise的resolve值解决。这意味着即使a失败了,只要b成功,c也会被执行,体现了“至少有一个成功即可”的逻辑。

Promise.any([a(), b()]).then(() => {
    c()
})

踏入异步之潮——探索JS Promise的奥秘

结语:希望这篇文章能对你有所帮助,我们下篇再见~

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