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)是作为参数传递给另一个函数的一个函数,然后在外部函数内部调用它来完成某种例程或操作。
同步回调示例:
Section titled “同步回调示例:”// `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 的异步回调
Section titled “使用 setTimeout 的异步回调”要使操作异步,我们可以使用诸如 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 的关键。
问题:回调地狱
Section titled “问题:回调地狱”当处理多个相互依赖的异步操作时,仅依赖回调可能导致代码深度嵌套,通常称为“回调地狱”(Callback Hell)或“末日金字塔”(Pyramid of Doom)。这使得代码难以阅读、调试和维护。
嵌套回调示例(示意):
Section titled “嵌套回调示例(示意):”setTimeout(function() { console.log("Operation 1 complete"); setTimeout(function() { console.log("Operation 2 complete"); setTimeout(function() { console.log("Operation 3 complete"); // ...等等 }, 1000); }, 1000);}, 1000);ES6 Promise:更清晰的方法
Section titled “ES6 Promise:更清晰的方法”Promise(Promise)是一个对象,代表一个异步操作的最终完成(或失败)及其结果值。Promise 提供了一种更清晰的方式来管理异步序列和错误处理。
Promise 可以处于以下三种状态之一:
- 待定(Pending): 初始状态,既未成功(
fulfilled)也未失败(rejected)。 - 成功(Fulfilled / Resolved): 操作成功完成,Promise 具有一个结果值。
- 失败(Rejected): 操作失败,Promise 具有失败的原因(一个错误)。
创建 Promise:
Section titled “创建 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: 20Promise 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)时执行回调。
ES2017 Async/Await:Promise 的语法糖
Section titled “ES2017 Async/Await:Promise 的语法糖”async 和 await 使异步代码看起来和行为更接近同步代码,这可以提高可读性和可维护性。
-
async函数隐式返回一个 Promise。 -
await关键字只能在async函数内部使用。它会暂停async函数的执行,直到被等待(awaited)的 Promise 达到 settled 状态。如果 Promise 成功(fulfilled),await返回成功值。如果失败(rejected),它会抛出失败的原因。
使用 async/await 的示例:
Section titled “使用 async/await 的示例:”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: 21Attempting 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。
Promise.all() 示例:
Section titled “Promise.all() 示例:”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 开发的基础,使得处理不会立即完成的操作的代码更具可读性、可维护性和健壮性。