Skip to content

ES6 - Promises

ES6+ - 异步 JavaScript:回调、Promise 和 Async/Await

Section titled “ES6+ - 异步 JavaScript:回调、Promise 和 Async/Await”

JavaScript 是单线程的,这意味着它一次只能执行一个操作。异步编程(Asynchronous programming)允许操作(例如从服务器获取数据、读取文件或定时器)在不阻塞主线程(main thread)的情况下进行,从而保持用户界面的响应性。ES6 引入了 Promise(Promise),作为处理异步操作的一种更健壮的方式,后来的 ECMAScript 版本(ES2017)引入了 async/await 语法,使使用 Promise 更加直观。

在 Promise 出现之前,回调是管理异步任务的主要方式。回调(callback)是作为参数传递给另一个函数的一个函数,然后在外部函数内部调用它来完成某种例程或操作。

// `notifyAll` 是一个接受两个回调函数的高阶函数。
function notifyAll(smsNotifier, emailNotifier) {
console.log('Starting notification process...');
smsNotifier(); // 执行第一个回调
emailNotifier(); // 执行第二个回调
}
notifyAll(
function() { // smsNotifier 回调
console.log("SMS sent.");
},
function() { // emailNotifier 回调
console.log("Email sent.");
}
);
console.log("End of script (同步部分)。");

同步回调的输出:

Starting notification process...
SMS sent.
Email sent.
End of script (synchronous part).

在此示例中,所有操作都是同步的。console.log(“End of script…”) 只有在 notifyAll 及其回调完成后才会执行。

要使操作异步,我们可以使用诸如 setTimeout 之类的机制,它会在指定的延迟后执行函数,而不会阻塞主线程。

function notifyAllAsync(smsNotifier, emailNotifier) {
// 模拟异步操作(例如,网络请求)
setTimeout(function() {
console.log('Starting asynchronous notification process...');
smsNotifier();
emailNotifier();
console.log('Asynchronous notification process finished.');
}, 2000); // 延迟 2000 毫秒(2 秒)后执行
}
notifyAllAsync(
function() {
console.log("Async SMS sent.");
},
function() {
console.log("Async Email sent.");
}
);
console.log("脚本结束(主线程继续执行)。这条会先打印!");

预期输出(异步块的顺序可能会因控制台实现而略有差异,但 ‘End of script’ 会先打印):

End of script (main thread continues). This will log first!
// 大约 2 秒后:
Starting asynchronous notification process...
Async SMS sent.
Async Email sent.
Asynchronous notification process finished.

setTimeout 将函数安排在稍后运行。主脚本继续执行,首先打印 “脚本结束…”。这种非阻塞行为是响应式 UI 的关键。

当处理多个相互依赖的异步操作时,仅依赖回调可能导致代码深度嵌套,通常称为“回调地狱”(Callback Hell)或“末日金字塔”(Pyramid of Doom)。这使得代码难以阅读、调试和维护。

setTimeout(function() {
console.log("Operation 1 complete");
setTimeout(function() {
console.log("Operation 2 complete");
setTimeout(function() {
console.log("Operation 3 complete");
// ...等等
}, 1000);
}, 1000);
}, 1000);

Promise(Promise)是一个对象,代表一个异步操作的最终完成(或失败)及其结果值。Promise 提供了一种更清晰的方式来管理异步序列和错误处理。

Promise 可以处于以下三种状态之一:

  • 待定(Pending): 初始状态,既未成功(fulfilled)也未失败(rejected)。
  • 成功(Fulfilled / Resolved): 操作成功完成,Promise 具有一个结果值。
  • 失败(Rejected): 操作失败,Promise 具有失败的原因(一个错误)。
function performAsyncTask(value) {
return new Promise((resolve, reject) => {
// 模拟一个异步任务
setTimeout(() => {
if (value >= 0) {
resolve(`Task successful with value: ${value}`); // 成功(fulfilled)Promise
} else {
reject(new Error('Task failed: Value cannot be negative.')); // 拒绝(rejected)Promise
}
}, 1000);
});
}

使用 .then() 和 .catch() 消费 Promise:

Section titled “使用 .then() 和 .catch() 消费 Promise:”
performAsyncTask(10)
.then(result => {
console.log('Success:', result); // 处理成功(fulfilled)的情况
return performAsyncTask(20); // 链式调用另一个 Promise
})
.then(nextResult => {
console.log('Next Success:', nextResult);
})
.catch(error => {
console.error('Error:', error.message); // 处理链中发生的任何拒绝(rejection)
})
.finally(() => {
console.log('Promise 达到 settled 状态(无论成功或失败)。'); // ES2018 新特性
});
// 失败示例
performAsyncTask(-5)
.then(result => {
console.log('这不会运行:', result);
})
.catch(error => {
console.error('来自负值的错误:', error.message);
});

第一个链(正值)的输出:

// 1 秒后:
Success: Task successful with value: 10
// 再过 1 秒后:
Next Success: Task successful with value: 20
Promise settled (either fulfilled or rejected).

第二个链(负值)的输出:

// 1 秒后:
Error from negative value: Task failed: Value cannot be negative.

.then() 用于安排 Promise 成功(fulfilled)时的回调。它可以进行链式调用。.catch() 用于处理错误(拒绝,rejections)。.finally() (ES2018) 在 Promise 无论成功还是失败(即 settled)时执行回调。

async 和 await 使异步代码看起来和行为更接近同步代码,这可以提高可读性和可维护性。

  • async 函数隐式返回一个 Promise。

  • await 关键字只能在 async 函数内部使用。它会暂停 async 函数的执行,直到被等待(awaited)的 Promise 达到 settled 状态。如果 Promise 成功(fulfilled),await 返回成功值。如果失败(rejected),它会抛出失败的原因。

function getSumAsync(n1, n2) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (n1 < 0 || n2 < 0) {
reject(new Error("Negative numbers not supported for sum."));
}
resolve(n1 + n2);
}, 500);
});
}
async function calculateSum() {
console.log('Starting calculations...');
try {
const sum1 = await getSumAsync(5, 6);
console.log('Sum 1:', sum1); // 11
const sum2 = await getSumAsync(sum1, 10);
console.log('Sum 2:', sum2); // 21
console.log('Attempting sum with negative number...');
const sum3 = await getSumAsync(sum2, -5); // 这会抛出错误
console.log('Sum 3 (不会执行到这里):', sum3);
} catch (error) {
console.error('计算错误:', error.message);
} finally {
console.log('Calculations finished.');
}
}
calculateSum();
console.log("Async 函数 calculateSum() 已调用,主线程继续执行...");

预期输出:

Async function calculateSum() invoked, main thread continues...
Starting calculations...
// 0.5 秒后
Sum 1: 11
// 再过 0.5 秒后
Sum 2: 21
Attempting sum with negative number...
// 再过 0.5 秒后
Calculation Error: Negative numbers not supported for sum.
Calculations finished.

async/await 极大地简化了 Promise 的使用,特别是对于顺序的异步操作和使用标准 try…catch 块进行错误处理。

常用的 Promise 工具方法:Promise.all() 和 Promise.race()

Section titled “常用的 Promise 工具方法:Promise.all() 和 Promise.race()”

JavaScript 提供了用于处理多个 Promise 的辅助函数:

  • Promise.all(iterable): 接受一个 Promise 可迭代对象,并返回一个 Promise。当可迭代对象中的所有 Promise 都成功(fulfilled)时,此 Promise 成功;只要其中一个 Promise 失败(rejected),此 Promise 就会失败。成功的值是输入 Promise 的成功值数组(顺序与输入一致)。
  • Promise.race(iterable): 接受一个 Promise 可迭代对象,并返回一个 Promise。一旦可迭代对象中的任何一个 Promise 成功(fulfilled)或失败(rejected),此 Promise 就会立即成功或失败,其值或原因取自第一个达到 settled 状态的 Promise。
const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'foo');
});
Promise.all([promise1, promise2, promise3])
.then(values => {
console.log(values); // [3, 42, "foo"]
})
.catch(error => {
console.error("其中一个 Promise 失败:", error);
});

Promise 以及 async/await 是现代异步 JavaScript 开发的基础,使得处理不会立即完成的操作的代码更具可读性、可维护性和健壮性。