前言
在计算机编程中,“异步”通常指的是一种编程模式,用于处理非阻塞的事件。异步编程允许程序在等待某些操作完成的同时继续执行其他任务,而不必停顿等待。这种模式可以通过回调函数、Promise对象、async/await等方式来实现。在面试当中会要求你自己写一个promise方法,所以说需要我们对于promise的基本知识理解透彻。本文将简单介绍通过创建一个
Promise对象来解决代码执行当中异步带来我们并不想要的执行结果。
如果该文章对你有用的话,还请三连,关注博主,之后我会继续更新并更加深入讲解Promise函数的用法以及内部的实现细节,能够让你实现面试中手搓简易的promise函数😘
异步
概念
请分析以下代码的执行结果:
function a (){
setTimeout(function(){
console.log('函数a执行结果')
},1000)//定时器,代码会在1秒后执行
}
function b (){
console.log('函数b执行结果')
}
a()
b()
很明显结果是“函数b执行结果、函数a执行结果”。但是我们是先调用a,再调用了b,那也就是说在等待a执行的时候,就执行了b因为执行b是不需要等待的。这就是js当中的异步,通过改变代码自上而下的执行顺序,优先执行运行时间短的函数,提升代码的运行效率。但是这也会带来问题。
问题
实际开发过程中经常会遇到,一个函数的执行需要上一个函数的执行结果
以下代码中函数a获取用户名,函数b需要通过函数a获取的用户名再来获取用户的基本信息,但是函数b的耗时更短,因为异步,虽然我们先调用a但是b会先执行完毕
function a ( ){
setTimeout(() => {
console.log('用户名');
},2000)
}
function b (){
setTimeout(() => {
console.log('用户的基本信息');
}, 1000);
}
a()
b()
//结果
//用户的基本信息
//用户名
回调
最初程序员为了解决这个问题,会通过回调的方法来解决
//回调解决
function a ( callback){
setTimeout(() => {
console.log('用户名');
callback()
},1000)
}
//当callback调用了,就算是callback函数没执行完,a也会出栈,不会造成内存泄露
function b (){
setTimeout(() => {
console.log('用户信息');
}, 2000);
}
a(b)
回调地狱(callback hell)
"回调地狱"通常是指在编程中出现的多层嵌套的回调函数,导致代码难以理解和维护的情况。这种情况通常发生在异步编程中
// 异步函数1
function asyncFunction1(callback) {
setTimeout(function () {
console.log("Async Function 1");
callback();
}, 1000);
}
// 异步函数2
function asyncFunction2(callback) {
setTimeout(function () {
console.log("Async Function 2");
callback();
}, 1000);
}
// 异步函数3
function asyncFunction3(callback) {
setTimeout(function () {
console.log("Async Function 3");
callback();
}, 1000);
}
// 回调地狱
asyncFunction1(function () {
asyncFunction2(function () {
asyncFunction3(function () {
console.log("Done");
});
});
});
在这个例子中,asyncFunction1、asyncFunction2 和 asyncFunction3 都是异步函数,它们在一段时间后执行,并且每个函数的完成都依赖于前一个函数的回调。
上述代码中的回调嵌套使得代码看起来复杂,可读性较差。这是因为每个异步函数的回调函数中包含了下一个异步函数的调用,形成了嵌套的结构。这种情况是回调地狱的一个典型示例。
也就是说当非常多的函数嵌套在一起的话,代码就非常难维护,因为这些函数依赖其他函数的执行结果,它们之间的关系相当于串联,一旦有一个函数出现问题,全部函数都会运行不了,也难以排查出问题函数
(串联电路出现问题,所有灯泡都不亮,也就不能确定哪个灯泡出了问题)
为了解决这个问题JS在后来更新中提供了Promise对象转化为链式调用来解决回调难以维护这个问题
Promise
我们通过一个有趣的小例子来介绍Promise对象的用法:
假设有个人张三要完成自己的婚姻大事,那么时间顺序是:相亲->结婚
分别用两个个函数代替,因为结婚可能比相亲时间短,会在相亲执行前执行完毕,不符合要求,所以我们通过Promise对象来解决
function xq (){
return new Promise((resolve, reject) => {//promise对象
setTimeout(() => {
console.log('张三相亲了!')
resolve('相亲成功了')
}, 2000);
})
}
function marry (){
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('张三结婚了!')
resolve('结婚成功')
}, 1000);
})
}
xq().then((res)=> {
console.log(res)
marry()
})
//执行结果
//张三相亲了
//相亲成功
//张三结婚了
结婚之后自然要生小宝宝了,我们添加一个baby函数在marry函数后面执行
function xq (){
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('张三相亲了!')
resolve('相亲成功了')
}, 2000);
})
}
function marry (){
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('张三结婚了!')
resolve('结婚成功')
}, 1000);
})
}
function baby (){
setTimeout(() => {
console.log('宝宝出生')
},500 );
}
xq().then((res1)=> {
console.log(res1)
marry()
})
.then((res2)=> {
console.log(res2)
baby()
})
//结果
//张三相亲了
//相亲成功
//undefined
//小宝宝出生了
//张三结婚了
异常
baby函数按照类似于marry函数的写法写在了后面为什么还是执行在前面 因为then应该是接在上一次执行函数返回的promise对象之后,实际上baby函数并没有接在marry函数返回的promise对象之后。 then()方法其实也会返回一个默认的Promise对象,这样执行了xq函数之后也就出现了两条支路,一条执行marry函数,一条执行baby函数,baby函数支路执行更快也就结果出现在前面
undefined的解释,因为promise对象中的resolve()会传出一个参数,而在相对应的then中会接受这个参数,我们写了打印语句就会打印,而因为这里baby函数是接在then返回的默认promise对象上,所以包裹baby函数的then方法并没有接受参数,所以打印没有赋值的形参,打印undefined
解决
我们将marry函数的执行结果返回,这样我们就用marry函数的Promise对象覆盖了then方法返回的默认Promise对象,这样就能够确保实现函数的链式调用
function xq (){
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('张三相亲了!')
resolve('相亲成功了')
}, 2000);
})
}
function marry (){
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('张三结婚了!')
resolve('结婚成功')
}, 1000);
})
}
function baby (){
setTimeout(() => {
console.log('宝宝出生')
},500 );
}
xq().then((res1)=> {
console.log(res1)
return marry()
})
.then((res2)=> {
console.log(res2)
baby()
})
//结果
//张三相亲了
//相亲成功
//张三结婚了
//结婚成功
//小宝宝出生了
promise.all与promise.race
实际的开发过程当中,也会存在确保一个函数在多个函数执行完成之后,再去执行;或者是等待多个函数执行的场景,哪个函数先执行完成,就在该函数之后执行该函数。promise中也提供了这两种情景的方法all,race
promise.all
function a (){
return new Promise((resolve, reject) =>{
setTimeout(() => {
console.log('a');
resolve()
}, 1000);
})
}
function b(){
return new Promise((resolve, reject) =>{
setTimeout(() => {
console.log('b');
resolve()
}, 500);
})
}
function c (){
console.log('c');
}
Promise.all([a(),b()]).then(c)
//结果b,a,c
- all里面的函数必须要返回promise对象的能力
- all必须要保证接收到的所有函数返回的promise对象状态均为resolve,then才会调用
- all方法里面的函数还是会按照异步执行
promise.race
function a (){
return new Promise((resolve, reject) =>{
setTimeout(() => {
console.log('a');
resolve()
}, 1000);
})
}
function b(){
return new Promise((resolve, reject) =>{
setTimeout(() => {
console.log('b');
resolve()
}, 500);
})
}
function c (){
console.log('c');
}
//竞速
Promise.race([a(),b()]).then(c)
//执行结果
b,c,a
- 在race中的promise对象,最先变成Fulfilled(已完成)即执行resolve()的对象在执行完之后会执行then语句,最后会执行race中剩下的promise对象
捕获异常
promise对象中resolve()是表示该对象成功执行,并且传入的参数会被then语句接受。还有一个reject()方法,表示该对象异常,如果不捕捉错误代码执行将会报错,加上catch语句传入的参数会被catch语句接受,不会报错,但是链接在该对象后面的promise对象都将不会执行
function a (){
return new Promise((resolve, reject) =>{
setTimeout(() => {
console.log('a');
// resolve()
reject('a错误');
}, 500);
})
}
function b(){
return new Promise((resolve, reject) =>{
setTimeout(() => {
console.log('b');
resolve()
}, 1100);
})
}
a().then((res)=>{
console.log(res);
return b()
})
.catch((err)=>{
console.log(err)
})
执行结果:
如果取消捕捉错误语句:
总结
javascript中代码的执行存在异步机制,会造成我们不想要的代码执行顺序,我们可以通过promise对象来解决这个问题。promise对象有两种状态成功resolve()与失败reject(),promise函数中有all,race方法满足不同场景的应用,以及catch语句捕捉错误