踏入异步之潮——探索JS Promise的奥秘
1、引言
在Web开发中,许多操作如网络请求、文件读写、定时器等都是耗时操作,如果使用同步执行模式,这些操作将会阻塞主线程,导致用户界面无法响应,也就是常说的“页面卡死”。因此,异步编程在JavaScript中不仅是必要的,而且是至关重要的,而Promise的出现让让我们能够以链式调用的方式组织代码,使复杂的异步逻辑也能如丝般顺滑。
2、什么是异步
我们先来看一份简单的代码,foo()设置一个1秒的定时器模拟网络请求需要的时间:
function foo() {
setTimeout(() => {
console.log('我的阿勒泰');
},1000)
}
function bar() {
console.log('好好看');
}
foo()
bar()
按理来说先执行foo()再执行bar(),应该会先打印“我的阿勒泰”,再打印“好好看”,但实际结果是
这是因为js是单线程的,一次只能执行一个任务;如果遇到需要耗时的任务,那就先跳过,先执行不耗时的代码,等到不耗时的代码执行完了,v8腾出手了,再执行耗时的代码,这就是异步。
与之相反的同步就是代码从上往下一步一步执行,谁也不跳过。这样就会大大降低代码的执行效率,所以异步编程是十分必要的。
3、Promise
3.1 情景引入
现在我们用两个函数模拟张三同志相亲结婚的过程
function xq() {
setTimeout(() => {
console.log('张三相亲了');
},2000)
}
function marry() {
setTimeout(() => {
console.log('张三结婚了');
},1000)
}
xq()
marry()
打印结果:
这显然不符合我们的预期,怎么能先结婚再相亲呢?为了张三能有个正常的成家过程,我们就要用到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()
})
这样就能按照我们预想的过程那样调用函数了:
②张三结完婚了,他还想要一个小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()
})
3.2.2 resolve传参
向resolve函数传递一个字符串作为参数值(在这个例子中是'相亲成功'
和'新婚快乐!'
),被后续的.then()
接收,充当回调函数的参数res
和res2
打印出来
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()
})
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);
})
补充:a().then((res) => { b() })也可以写成a().then(b)
小总结:
- then((res) => {}) res是promise中resolve(xx)出来的值
- 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()
})
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()
})
3.3.3 Promise.any()
Promise.any([a(), b()])
只要其中任何一个Promise解决(非reject状态),就立即以该Promise的resolve值解决。这意味着即使a失败了,只要b成功,c也会被执行,体现了“至少有一个成功即可”的逻辑。
Promise.any([a(), b()]).then(() => {
c()
})
结语:希望这篇文章能对你有所帮助,我们下篇再见~
转载自:https://juejin.cn/post/7374419258397360140