JS:用promise对象解决异步问题(一)

272 阅读8分钟

前言

在计算机编程中,“异步”通常指的是一种编程模式,用于处理非阻塞的事件。异步编程允许程序在等待某些操作完成的同时继续执行其他任务,而不必停顿等待。这种模式可以通过回调函数、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");
        });
    });
});

在这个例子中,asyncFunction1asyncFunction2asyncFunction3 都是异步函数,它们在一段时间后执行,并且每个函数的完成都依赖于前一个函数的回调。

上述代码中的回调嵌套使得代码看起来复杂,可读性较差。这是因为每个异步函数的回调函数中包含了下一个异步函数的调用,形成了嵌套的结构。这种情况是回调地狱的一个典型示例。 image.png

也就是说当非常多的函数嵌套在一起的话,代码就非常难维护,因为这些函数依赖其他函数的执行结果,它们之间的关系相当于串联,一旦有一个函数出现问题,全部函数都会运行不了,也难以排查出问题函数

(串联电路出现问题,所有灯泡都不亮,也就不能确定哪个灯泡出了问题)

为了解决这个问题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)
})

执行结果:

image.png

如果取消捕捉错误语句:

image.png

总结

javascript中代码的执行存在异步机制,会造成我们不想要的代码执行顺序,我们可以通过promise对象来解决这个问题。promise对象有两种状态成功resolve()与失败reject(),promise函数中有all,race方法满足不同场景的应用,以及catch语句捕捉错误

如果该文章对你有用的话,还请三连,关注博主,之后我会继续更新并更加深入讲解Promise函数的用法以及内部的实现细节,能够让你实现面试中手搓简易的promise函数😘