likes
comments
collection
share

面试必考题之Promise——解决回调地狱

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

前言

在当今高度依赖于交互性和实时数据处理的互联网应用开发中,异步编程已成为不可或缺的一部分。它允许程序在等待某个耗时操作(例如网络请求、数据库查询)完成的同时,继续执行其他任务,从而显著提升了应用程序的响应速度与用户体验。然而,随着异步操作的增多,传统基于回调函数的处理方式逐渐暴露出了其局限性,尤其是当多个异步操作需要顺序执行或相互依赖时,"回调地狱"——即层层嵌套的回调函数,使得代码变得难以阅读、理解和维护。

而正是因为许多程序员受到回调地狱的侵害

ES6中的原生对象Promise应运而生

正文

要讲Promise,我们先搞清楚几个基本概念

什么是异步

异步(Asynchronous)编程是一种编程模式,它允许程序在等待某个操作(比如I/O操作、网络请求、数据库查询等)完成的同时,继续执行其他任务,而不需要阻塞或暂停整个程序的执行。在现代软件开发,尤其是在Web开发中,异步编程尤为重要,因为它有助于提升应用的响应性和用户体验。

为什么需要异步?

计算机执行任务时,资源(如CPU、内存、网络)的使用是有限的。在执行一些耗时操作(如读取硬盘上的大文件、跨网络请求数据)时,如果采用同步方式,程序会等待这些操作完成才继续执行下一步,这期间CPU等资源可能处于闲置状态,造成效率低下。特别是在单线程环境中(如JavaScript在浏览器中的执行环境),同步操作会导致整个页面或应用暂时无响应,用户体验不佳。

现在我们来举个例子

var a = 1

setTimeout(()=>{
   console.log(a);
},1000)
console.log(a);

这就是一个最为简单的异步任务

我们用setTimeout()将指定程序设置为1秒后执行,这样允许程序在等待某些操作(如I/O操作、长时间计算或定时任务)完成的同时,继续执行其他任务,而不是阻塞主线程等待该操作结束。这对于保持用户界面的响应性和提高程序整体效率至关重要,*尤其是在单线程环境下,而JavaScript在浏览器环境下就是一个单线程环境

在这一段代码下:

var data = null
function a() {
    setTimeout(function() {
        data = 'hello'
        b()
    },1000)
}

function b() {
    console.log(data);
}
a()

b函数的实现需要通过a函数的异步任务执行时才能够执行,

否则单独调用b函数则会输出null

这样一个小型的回调程序还算简洁

但若是像这样呢?

function a(cbB,cbC,cbD) {
    cbB(cbC,cbD)
}
function b(cb,cbD) {
    cb(cbD)
}

function c(cb) {
    cb()
}

function d() {

}
a(b,c,d)

是不是就开始发晕?

而在大型企业中,比这样复杂的需求多得多。

程序员们经常需要在一个大型的封装函数里使用几十上百个参数

而当上一任程序员把程序交给你后,你感动吗?

那肯定是不敢动啊!这就是回调地狱

由于过度使用回调函数来处理一系列的异步操作,导致代码结构变得极其复杂、难以理解和维护。

所以ES6中处理异步任务的对象应运而生——Promise

Promise是JavaScript中用于处理异步操作的一个对象,它代表了一个最终可能会完成(或失败)的异步操作及其结果。Promise的设计旨在让异步代码的编写更加有序和易于理解,相较于传统的回调函数,它能有效地解决“回调地狱”问题,使异步逻辑看起来更像是同步代码。

Promise的主要特点包括:

  1. 状态不可变性:Promise有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。一旦Promise从pending变为fulfilled或rejected,这个状态就固定不变了,任何操作都无法再改变这个状态,这确保了异步操作结果的确定性。
  2. 链式调用:Promise通过.then.catch方法支持链式操作,使得多个异步操作可以按照一定的顺序依次执行,每个步骤都可以根据前一个步骤的成功或失败来决定自己的行为,大大增强了代码的可读性和灵活性。
  3. 错误处理集中:使用.catch方法可以统一处理Promise链中的错误,避免了在每个回调函数中都需要单独处理错误的情况,使得错误处理更加集中和统一。
  4. 避免回调地狱:通过Promise的链式调用和错误处理机制,可以有效减少因多层嵌套回调函数而形成的“回调地狱”,使得代码结构更加扁平和清晰。
  5. 静态方法:Promise还提供了静态方法如Promise.all()Promise.race()Promise.resolve()Promise.reject()等,这些方法进一步丰富了异步编程的工具集,使得处理并发异步操作或快速创建已完成/已拒绝的Promise变得简单。

用文字来描述有些苍白,我们现在来用代码说话

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

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

xq()
marry()

我们定义两个函数,一个叫相亲xq(),一个叫结婚marry()

现在我们运行这两个函数,由于setTimeout()处理异步任务,

面试必考题之Promise——解决回调地狱 就会先结婚后相亲,这怎么合理呢?

于是,为了更好的处理异步任务,我们使用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)
    })
    
}
xq().then(() => {
    marry()
})

我们使用Promise()构造函数生成一个promise实例对象,并返回这样一个实例对象

而在setTimeout中的resolve()执行前,该函数内的promise对象一直是处于pending状态

在函数中的resolve()执行后,函数内的promise对象就变成了resolve状态

接着.then()开始链式调用,访问到xq()传回来了resolve状态,便开始执行内部函数marry()

最后就先相亲后结婚了

面试必考题之Promise——解决回调地狱

我们再添加一个函数baby()体验一下更加高级的链式调用

function baby() {
    console.log("宝宝");
}

这个函数并不是异步的,但是我们想让他异步执行,所以我们继续使用promise实例对象让它运行

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

})

这样写是完全没有毛病的,但是一层包着一层的写法仍然看着有些繁琐

所以我们还可以写成这样

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

promise里的.then()是可以这样调用的,但是有一个问题,你会发现在这个程序中输出的是

面试必考题之Promise——解决回调地狱

为什么呢?

是因为当.then()是有自己的默认返回值的

第二个.then()访问到then返回的是pending会进行链式访问,访问到最上面xq()resolve状态,所以宝宝会比结婚先输出

而当我们把代码改成

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

.then()检测到内部函数有返回值,便不使用自身的返回值,而使用内部函数的返回值,让下一个.then()访问到它后会等待它的resolve状态再运行。

promise对象中还有一个catch异常处理函数,它的使用一样是异步的

它是为了捕获promise中的另一个状态reject而生的

它的使用可以看一下这样一串代码

var data = null
function a() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log('a complete');
            data = 'hhhh'
            reject()

        }, 1000)
    })
}

function b() {
    console.log(data);
}



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

本身是会报错的,但是由于有了catch()进行了异常处理,使得不会输出报错,而是输出程序中设定的内容。

总结

Promise通过提供一种标准化的方式来组织异步操作,不仅提高了代码的可维护性和可读性,也使得开发者能够以更加符合直觉的方式处理复杂的异步逻辑。它是现代JavaScript异步编程的核心之一,广泛应用于网络请求、文件操作、定时任务等各种需要异步处理的场景。

求点赞评论收藏,有问题随时私信博主!

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