MST

星途 面试题库

面试题:JavaScript中Async/Await与Promise的关系及应用场景对比

解释JavaScript中Async/Await与Promise之间的关系,阐述它们各自的优势和适用场景。假设你正在开发一个需要依次调用多个异步API的项目,用Async/Await和Promise分别实现,并分析两种实现方式在代码可读性和错误处理方面的差异。
37.5万 热度难度
编程语言JavaScript

知识考点

AI 面试

面试题答案

一键面试

Async/Await与Promise的关系

  • Async/Await基于Promise:Async函数是一个异步函数,它返回一个Promise对象。在async函数内部,await只能用于Promise对象上,它暂停async函数的执行,等待Promise解决(resolved)或拒绝(rejected),然后恢复async函数的执行并返回已解决的值或抛出被拒绝的原因。
  • 语法糖:Async/Await本质上是对Promise的语法糖,使异步代码看起来更像同步代码,极大地提高了代码的可读性。

各自的优势

  • Promise优势
    • 链式调用:通过.then()方法实现链式调用,使得多个异步操作可以按顺序执行,每个.then()方法返回一个新的Promise,方便对异步操作进行组合和管理。
    • 支持并行执行:可以使用Promise.all()或Promise.race()等方法来处理多个Promise并行执行的情况。
  • Async/Await优势
    • 代码更简洁直观:使用类似同步代码的结构,避免了Promise链式调用中可能出现的“回调地狱”问题,使代码逻辑更加清晰。
    • 错误处理更简单:在async函数中,可以使用try...catch块来捕获错误,比Promise的.catch()方法在某些情况下更直观和统一。

适用场景

  • Promise:适用于需要处理多个异步操作并行执行,或者对异步操作的控制流要求较为灵活的场景,例如同时发起多个API请求并等待所有请求完成。
  • Async/Await:适用于需要按顺序依次执行多个异步操作,并且希望代码结构更接近同步代码,提高代码可读性的场景,比如需要依次调用多个依赖的API。

依次调用多个异步API的实现

假设存在三个异步API函数 api1()api2()api3(),每个函数返回一个Promise对象。

使用Promise实现

function api1() {
    return new Promise((resolve) => {
        setTimeout(() => {
            console.log('api1执行完毕');
            resolve('api1结果');
        }, 1000);
    });
}

function api2(result1) {
    return new Promise((resolve) => {
        setTimeout(() => {
            console.log('api2执行完毕,依赖api1结果:', result1);
            resolve('api2结果');
        }, 1000);
    });
}

function api3(result2) {
    return new Promise((resolve) => {
        setTimeout(() => {
            console.log('api3执行完毕,依赖api2结果:', result2);
            resolve('api3结果');
        }, 1000);
    });
}

api1()
   .then(result1 => api2(result1))
   .then(result2 => api3(result2))
   .then(finalResult => {
        console.log('最终结果:', finalResult);
    })
   .catch(error => {
        console.error('发生错误:', error);
    });

使用Async/Await实现

function api1() {
    return new Promise((resolve) => {
        setTimeout(() => {
            console.log('api1执行完毕');
            resolve('api1结果');
        }, 1000);
    });
}

function api2(result1) {
    return new Promise((resolve) => {
        setTimeout(() => {
            console.log('api2执行完毕,依赖api1结果:', result1);
            resolve('api2结果');
        }, 1000);
    });
}

function api3(result2) {
    return new Promise((resolve) => {
        setTimeout(() => {
            console.log('api3执行完毕,依赖api2结果:', result2);
            resolve('api3结果');
        }, 1000);
    });
}

async function main() {
    try {
        const result1 = await api1();
        const result2 = await api2(result1);
        const finalResult = await api3(result2);
        console.log('最终结果:', finalResult);
    } catch (error) {
        console.error('发生错误:', error);
    }
}

main();

代码可读性和错误处理差异

  • 代码可读性
    • Promise:链式调用在处理多个异步操作时,随着操作数量的增加,代码会变得冗长,尤其是嵌套多层时,会出现“回调地狱”问题,使得代码逻辑不清晰。
    • Async/Await:使用同步代码的结构,通过await暂停异步操作,代码结构更清晰,易于理解和维护,提高了代码的可读性。
  • 错误处理
    • Promise:通过.catch()方法捕获整个链式调用中的错误,但如果链式调用较长,定位具体出错位置可能相对困难,并且每个.then()方法内部的同步代码错误无法被.catch()捕获,需要在每个.then()内部单独处理。
    • Async/Await:可以使用try...catch块捕获整个async函数内的所有错误,无论是异步操作还是同步代码的错误,都可以统一处理,使错误处理更直观和简洁。