跳到主要内容

async/await 与 Promise 的对比

async/await 是 JavaScript 中处理异步操作的语法糖,它基于 Promise 对象实现。下面我们通过对比 async/await 与 Promise,深入理解它们的特点和用法。

async/await 的特点

await 是一个操作符,用于等待 Promise 对象的处理结果。它必须在 async 函数中使用。await 表达式的运算结果取决于它等待的 Promise 对象的状态:

如果 Promise 处于 pending 状态,await 表达式会暂停 async 函数的执行,直到 Promise 状态发生变化。 如果 Promise 处于 fulfilled 状态,await 表达式的计算结果是 Promise 的值。 如果 Promise 处于 rejected 状态,await 表达式会抛出错误。

async 函数本质上是一个返回 Promise 对象的函数。async 函数内部可以使用 await 操作符,让函数暂停执行,直到一个 Promise 对象 resolve,然后得到 resolve 的值,再继续执行。

与 Promise 的对比

我们可以将 async/await 与 Promise 进行对比:

async 可以看作是将函数返回值使用 Promise.resolve()包装了下。 await 可以理解为 Promise 的 then 方法的语法糖。

下面是一个使用 Promise 和 async/await 实现相同功能的例子:

// Promise实现
new Promise((resolve) => {
console.log(1);
resolve('2');
}).then((value) => {
console.log(value);
});

// async/await实现
async function asyncFn() {
let a = await new Promise((resolve) => {
setTimeout(() => {
resolve('1');
}, 1000);
});
console.log(a);

let b = await new Promise((resolve) => {
setTimeout(() => {
resolve('2');
}, 1000);
});
console.log(b);

let c = await new Promise((resolve) => {
setTimeout(() => {
resolve('3');
}, 1000);
});
console.log(c);

let d = await new Promise((resolve) => {
setTimeout(() => {
resolve('4');
}, 1000);
});
console.log(d);
}

asyncFn();

在上面的示例中,我们可以看到 async/await 让异步代码的写法更加清晰和直观。

实战示例

延迟函数

我们可以使用 async/await 实现一个延迟函数,让程序暂停一段时间后再继续执行:

async function sleep(delay) {
return new Promise((resolve) => {
setTimeout(resolve, delay);
});
}

async function showNumbers() {
for (let i of [1, 2, 3, 4, 5]) {
await sleep(1000);
console.log(i);
}
}

showNumbers();

在上面的代码中,sleep 函数返回一个 Promise 对象,它在指定的延迟时间后 resolve。在 showNumbers 函数中,我们使用 for...of 循环遍历数组,每次循环都等待 1 秒钟,然后打印当前的数字。

与 Class 结合使用

async/await 也可以与 ES6 的 Class 语法结合使用:

class User {
constructor(name) {
this.name = name;
}

then(resolve) {
resolve(this.name);
}
}

async function getUser() {
let user = await new User('zhangsan');
console.log(user);
}

getUser();

在上面的代码中,我们定义了一个 User 类,它的构造函数接收一个 name 参数。然后我们给 User 类添加了一个 then 方法,让它可以被 await。在 getUser 函数中,我们使用 await 关键字等待 User 对象的 then 方法执行完成,然后得到 resolve 的值,也就是用户的名字。

错误处理

使用 async/await 时,我们可以使用 try/catch 语句来捕获错误:

async function fn() {
try {
console.log(a);
} catch (error) {
console.log(error);
}
}

fn();

在上面的代码中,当 fn 函数执行时,如果 a 变量没有定义,就会抛出一个错误。我们使用 try/catch 语句捕获这个错误,然后打印出来。

如果 async 函数内部没有使用 try/catch 捕获错误,那么错误会被 async 函数返回的 Promise 对象 reject:

async function fn() {
console.log(a);
}

fn()
.then((value) => {
console.log('resolved', value);
})
.catch((err) => {
console.log('rejected', err);
});

在上面的代码中,当 fn 函数抛出错误时,错误会被 catch 方法捕获,然后打印出来。

并行执行

async/await 也可以实现并行执行多个异步操作:

function delay(time) {
return new Promise((resolve) => {
setTimeout(resolve, time);
});
}

async function fn() {
let start = Date.now();

await Promise.all([delay(1000), delay(2000)]);

let end = Date.now();
console.log(end - start);
}

fn();

在上面的代码中,我们定义了一个 delay 函数,它返回一个 Promise 对象,在指定的时间后 resolve。然后我们在 fn 函数中使用 Promise.all 方法并行执行两个 delay 函数,等待它们都完成后,计算并打印出总共耗时。

我们也可以使用 Promise.race 方法等待多个异步操作中的一个完成:

function delay(time) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(time);
}, time);
});
}

async function fn() {
let result = await Promise.race([delay(1000), delay(2000)]);

console.log(result);
}

fn();

在上面的代码中,Promise.race 方法接收一个 Promise 对象数组,等待其中一个 Promise 对象 resolve,然后返回这个 Promise 对象的值。

总结

await 关键字只能在 async 函数中使用。

async 函数返回一个 Promise 对象。

可以使用 try/catch 语句捕获 async 函数中的错误,也可以使用 Promise 的 catch 方法。

多个异步操作可以使用 Promise.all 和 Promise.race 来并行执行。

在实际开发中,我们应该根据具体的需求选择使用 Promise 还是 async/await。对于一些简单的异步操作,使用 Promise 可能更加方便。而对于一些复杂的异步操作,使用 async/await 可以让代码更加清晰和易于阅读。