在学习 JavaScript 异步编程的过程中,我一直有个困惑:为什么异步方案要经历从 Callback 到 Promise 再到 async/await 的演进?每次被问到"Promise 解决了什么问题"时,我总觉得自己的回答流于表面。直到最近深入研究了 Promise 的设计思想和 async/await 的实现原理,才开始理解这些看似简单的 API 背后隐藏的深刻智慧。这篇文章是我的学习总结,希望能和你一起探索异步编程的本质。
问题的起源:回调地狱到底"地狱"在哪?
回调函数的基本形式
在 Promise 出现之前,JavaScript 处理异步操作主要依赖回调函数:
// 环境:Node.js / 浏览器
// 场景:传统回调函数处理异步
function fetchUser(userId, callback) {
setTimeout(() => {
const user = { id: userId, name: 'Alice' };
callback(null, user);
}, 100);
}
fetchUser(1, (error, user) => {
if (error) {
console.error(error);
return;
}
console.log(user);
});
这看起来还挺简单的,那为什么会有"回调地狱"这个说法呢?
回调地狱的真实面貌
当业务逻辑变复杂,需要多个异步操作依次执行时,问题就来了:
// 环境:Node.js / 浏览器
// 场景:多层嵌套的回调,形成"金字塔"结构
fetchUser(1, (err, user) => {
if (err) {
console.error('Failed to fetch user:', err);
return;
}
fetchUserPosts(user.id, (err, posts) => {
if (err) {
console.error('Failed to fetch posts:', err);
return;
}
fetchPostComments(posts[0].id, (err, comments) => {
if (err) {
console.error('Failed to fetch comments:', err);
return;
}
console.log('Comments:', comments);
// 如果还有更多层级...代码会继续向右缩进
});
});
});
回调地狱的本质问题
经过研究,我发现"回调地狱"的问题不仅仅是代码难看,更深层的问题包括:
1. 控制流不直观
人类的思维是线性的:"先做 A,再做 B,最后做 C"。但回调函数的嵌套破坏了这种线性思维,代码的执行顺序和阅读顺序不一致。
2. 错误处理重复且容易遗漏
每一层回调都需要单独处理错误,很容易漏掉某个 if (err) 判断。而且错误处理逻辑分散在各处,难以统一管理。
3. 难以组合和复用
如果想要并行执行多个异步操作,或者在某些条件下跳过某些步骤,用回调函数很难优雅地实现。
4. 信任问题
当你把回调函数传给第三方库时,你无法控制这个回调会被调用几次、什么时候调用,甚至是否会被调用。
Promise:一种全新的异步解决方案
Promise 的核心概念
Promise 是一个表示异步操作最终结果的对象。我的理解是,Promise 把"未来的值"封装成了一个对象,让你可以用同步的方式操作异步的结果。
// 环境:浏览器 / Node.js 18+
// 场景:Promise 基础用法
// 创建一个 Promise
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
const success = true;
if (success) {
resolve('Operation succeeded');
} else {
reject('Operation failed');
}
}, 1000);
});
// 使用 Promise
promise
.then(result => {
console.log(result); // 'Operation succeeded'
})
.catch(error => {
console.error(error);
});
Promise 的三个状态
Promise 有三个状态,且状态转换是单向不可逆的:
stateDiagram-v2
[*] --> Pending
Pending --> Fulfilled: resolve()
Pending --> Rejected: reject()
Fulfilled --> [*]
Rejected --> [*]
- Pending(待定) :初始状态,既没有成功也没有失败
- Fulfilled(已兑现) :操作成功完成
- Rejected(已拒绝) :操作失败
为什么状态要设计成不可逆的?
这个设计有深刻的考量:
- 可预测性:一旦 Promise 确定了结果,这个结果就永远不会改变。你可以安全地多次调用
.then(),每次都会得到相同的结果。 - 避免竞态条件:如果状态可以反复改变,就会出现"resolve 之后又 reject"的情况,导致不可预测的行为。
- 符合函数式编程思想:Promise 是 immutable(不可变)的,这让异步操作的结果可以像纯函数一样被推理和组合。
// 环境:浏览器 / Node.js 18+
// 场景:Promise 状态不可逆的演示
const promise = new Promise((resolve, reject) => {
resolve('first value');
resolve('second value'); // 无效,状态已经是 fulfilled
reject('error'); // 无效,状态已经是 fulfilled
});
promise.then(value => {
console.log(value); // 输出:'first value'
});
Promise 链式调用的魔法
Promise 最强大的特性之一是链式调用。每个 .then() 都会返回一个新的 Promise:
// 环境:浏览器 / Node.js 18+
// 场景:Promise 链式调用
fetch('/api/user')
.then(response => response.json())
.then(user => fetch(`/api/posts?userId=${user.id}`))
.then(response => response.json())
.then(posts => {
console.log('User posts:', posts);
})
.catch(error => {
console.error('Error in chain:', error);
});
这段代码展示了 Promise 如何解决回调地狱的问题:
- 扁平化结构:不再需要嵌套,代码向下展开而不是向右缩进
- 统一错误处理:只需要一个
.catch()就能捕获链条中任何一步的错误 - 清晰的控制流:从上到下阅读,符合人类的线性思维
关键理解:每个 .then() 返回的是新的 Promise
// 环境:浏览器 / Node.js 18+
// 场景:理解 .then() 返回新 Promise
const promise1 = Promise.resolve(1);
const promise2 = promise1.then(value => value + 1);
const promise3 = promise2.then(value => value + 1);
console.log(promise1 === promise2); // false
console.log(promise2 === promise3); // false
// 每个 .then() 都创建了新的 Promise
Promise 的错误处理机制
Promise 的错误处理有一个很优雅的设计:错误会沿着链条"冒泡",直到被捕获。
// 环境:浏览器 / Node.js 18+
// 场景:Promise 错误冒泡
Promise.resolve()
.then(() => {
console.log('Step 1');
return 'result 1';
})
.then(() => {
console.log('Step 2');
throw new Error('Something went wrong');
})
.then(() => {
console.log('Step 3'); // 不会执行
})
.catch(error => {
console.error('Caught:', error.message); // 捕获错误
})
.then(() => {
console.log('Step 4'); // 会继续执行
});
// 输出:
// Step 1
// Step 2
// Caught: Something went wrong
// Step 4
.catch() vs .then(null, onRejected) 的区别
// 环境:浏览器 / Node.js 18+
// 场景:两种错误处理方式的区别
// 方式 1:使用 .catch()
Promise.resolve()
.then(() => {
throw new Error('Error in then');
})
.catch(error => {
console.log('Caught by catch:', error.message);
});
// 方式 2:使用 .then(onFulfilled, onRejected)
Promise.resolve()
.then(
() => {
throw new Error('Error in then');
},
error => {
// 这个回调捕获不到上面 then 中的错误!
console.log('Caught by then:', error.message);
}
);
// 输出:
// Caught by catch: Error in then
// Uncaught Error: Error in then
区别在于:.then(onFulfilled, onRejected) 的 onRejected 只能捕获前面 Promise 的错误,无法捕获 onFulfilled 中抛出的错误。而 .catch() 可以捕获前面链条中任何位置的错误。
Promise 工具方法:并发控制的利器
Promise.all:全部成功才成功
// 环境:浏览器 / Node.js 18+
// 场景:并行执行多个请求,等待全部完成
const promise1 = fetch('/api/users');
const promise2 = fetch('/api/posts');
const promise3 = fetch('/api/comments');
Promise.all([promise1, promise2, promise3])
.then(([users, posts, comments]) => {
console.log('All data loaded:', { users, posts, comments });
})
.catch(error => {
console.error('At least one request failed:', error);
});
特点:
- 所有 Promise 都成功时,返回所有结果的数组
- 只要有一个失败,立即 reject,其他 Promise 继续执行但结果被忽略
- 适用场景:多个请求相互依赖,缺一不可
Promise.allSettled:不管成败,全部等待
// 环境:浏览器 / Node.js 18+
// 场景:批量操作,即使部分失败也要知道结果
const promises = [
fetch('/api/user/1'),
fetch('/api/user/2'),
fetch('/api/user/999'), // 假设这个会失败
];
Promise.allSettled(promises)
.then(results => {
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
console.log(`Promise ${index} succeeded:`, result.value);
} else {
console.log(`Promise ${index} failed:`, result.reason);
}
});
});
// 输出:
// Promise 0 succeeded: [Response object]
// Promise 1 succeeded: [Response object]
// Promise 2 failed: [Error object]
特点:
- 等待所有 Promise 完成(无论成功或失败)
- 返回数组,每个元素是
{ status: 'fulfilled'|'rejected', value|reason } - 适用场景:批量操作,需要知道每个操作的结果
Promise.race:谁快听谁的
// 环境:浏览器 / Node.js 18+
// 场景:请求超时控制
function fetchWithTimeout(url, timeout = 5000) {
const fetchPromise = fetch(url);
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error('Request timeout')), timeout);
});
return Promise.race([fetchPromise, timeoutPromise]);
}
fetchWithTimeout('/api/slow-endpoint', 3000)
.then(response => console.log('Success:', response))
.catch(error => console.error('Failed:', error.message));
特点:
- 第一个完成的 Promise 决定结果
- 适用场景:超时控制、取最快响应
Promise.any:有一个成功就成功
// 环境:浏览器 / Node.js 18+
// 场景:请求多个镜像服务器,使用最快的响应
const mirrors = [
fetch('https://mirror1.com/api/data'),
fetch('https://mirror2.com/api/data'),
fetch('https://mirror3.com/api/data'),
];
Promise.any(mirrors)
.then(response => {
console.log('Got response from fastest mirror:', response);
})
.catch(error => {
console.error('All mirrors failed:', error);
});
特点:
- 只要有一个成功就返回
- 所有都失败才 reject,返回 AggregateError
- 适用场景:多个备选方案,取最快成功的
async/await:Promise 的语法糖
async/await 的本质
async/await 让异步代码看起来像同步代码,但它的本质是 Promise + Generator 的语法糖。
// 环境:浏览器 / Node.js 18+
// 场景:async/await 基础用法
// async 函数总是返回 Promise
async function fetchUser(id) {
return { id, name: 'Alice' };
}
// 等价于
function fetchUser(id) {
return Promise.resolve({ id, name: 'Alice' });
}
// 调用
fetchUser(1).then(user => console.log(user));
await 的执行机制
await 做了三件事:
- 等待 Promise 完成
- 返回 Promise 的结果(如果成功)
- 将 await 后面的代码转换成
.then()回调
// 环境:浏览器 / Node.js 18+
// 场景:await 的执行机制
async function example() {
console.log('1');
const result = await Promise.resolve('2');
console.log(result);
console.log('3');
}
// 等价转换
function example() {
console.log('1');
return Promise.resolve('2').then(result => {
console.log(result);
console.log('3');
});
}
example();
console.log('4');
// 输出:1 → 4 → 2 → 3
为什么 await 后面的代码会变成微任务?
因为它被转换成了 .then() 回调,而 .then() 回调会被放入微任务队列。这就是为什么 console.log('4') 会在 console.log(result) 之前执行。
async/await 的错误处理
async/await 让我们可以用传统的 try-catch 处理异步错误:
// 环境:浏览器 / Node.js 18+
// 场景:async/await 错误处理
async function fetchData() {
try {
const response = await fetch('/api/data');
const data = await response.json();
return data;
} catch (error) {
console.error('Failed to fetch data:', error);
throw error; // 可以选择重新抛出或返回默认值
}
}
但是,每个 await 都写 try-catch 很繁琐。有更优雅的方式吗?
一个常见的模式是创建一个辅助函数:
// 环境:浏览器 / Node.js 18+
// 场景:优雅的错误处理辅助函数
function to(promise) {
return promise
.then(data => [null, data])
.catch(error => [error, null]);
}
// 使用
async function loadUser() {
const [error, user] = await to(fetch('/api/user'));
if (error) {
console.error('Failed to load user:', error);
return;
}
console.log('User loaded:', user);
}
这个模式受到 Go 语言错误处理的启发,让错误处理更加显式和可控。
实际应用场景
场景 1:串行 vs 并行
这是一个容易踩坑的地方。
// 环境:浏览器 / Node.js 18+
// 场景:对比串行和并行的执行时间
// ❌ 串行执行:总耗时 = 所有请求耗时之和
async function serialFetch() {
const start = Date.now();
const user = await fetch('/api/user'); // 等待 100ms
const posts = await fetch('/api/posts'); // 等待 100ms
const comments = await fetch('/api/comments'); // 等待 100ms
console.log('Serial time:', Date.now() - start); // 约 300ms
}
// ✅ 并行执行:总耗时 = 最慢的请求耗时
async function parallelFetch() {
const start = Date.now();
const [user, posts, comments] = await Promise.all([
fetch('/api/user'),
fetch('/api/posts'),
fetch('/api/comments')
]);
console.log('Parallel time:', Date.now() - start); // 约 100ms
}
何时选择串行?何时选择并行?
- 串行:当后面的请求依赖前面的结果时
- 并行:当请求之间相互独立时
// 环境:浏览器 / Node.js 18+
// 场景:混合使用串行和并行
async function smartFetch() {
// 第一步:获取用户信息(必须先执行)
const user = await fetch(`/api/user/${userId}`);
// 第二步:并行获取用户的多个资源(互不依赖)
const [posts, friends, settings] = await Promise.all([
fetch(`/api/posts?userId=${user.id}`),
fetch(`/api/friends?userId=${user.id}`),
fetch(`/api/settings?userId=${user.id}`)
]);
return { user, posts, friends, settings };
}
场景 2:并发限制
在实际开发中,我们常常需要限制并发数量,避免一次性发起太多请求。
// 环境:浏览器 / Node.js 18+
// 场景:实现一个并发限制的请求调度器
class RequestScheduler {
constructor(maxConcurrent = 3) {
this.maxConcurrent = maxConcurrent;
this.currentCount = 0;
this.queue = [];
}
async add(promiseCreator) {
// 如果达到并发上限,加入队列等待
while (this.currentCount >= this.maxConcurrent) {
await new Promise(resolve => this.queue.push(resolve));
}
this.currentCount++;
try {
return await promiseCreator();
} finally {
this.currentCount--;
// 通知队列中的下一个请求
const resolve = this.queue.shift();
if (resolve) resolve();
}
}
}
// 使用示例
const scheduler = new RequestScheduler(3);
const urls = Array.from({ length: 10 }, (_, i) => `/api/item/${i}`);
async function fetchAll() {
const promises = urls.map(url =>
scheduler.add(() => fetch(url))
);
const results = await Promise.all(promises);
console.log('All fetched:', results);
}
// 最多同时只有 3 个请求在执行
fetchAll();
场景 3:重试逻辑
处理不稳定的网络请求时,重试机制很有用:
// 环境:浏览器 / Node.js 18+
// 场景:带重试机制的请求函数
async function fetchWithRetry(url, options = {}) {
const { maxRetries = 3, delay = 1000 } = options;
for (let i = 0; i <= maxRetries; i++) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return response;
} catch (error) {
// 如果是最后一次重试,抛出错误
if (i === maxRetries) {
throw error;
}
// 等待一段时间后重试
console.log(`Retry ${i + 1}/${maxRetries} after ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
// 使用
fetchWithRetry('/api/unstable-endpoint', { maxRetries: 5, delay: 2000 })
.then(response => console.log('Success:', response))
.catch(error => console.error('All retries failed:', error));
常见陷阱与最佳实践
陷阱 1:forEach 中的 async/await
// 环境:浏览器 / Node.js 18+
// 场景:forEach 不会等待 async 函数
// ❌ 错误做法:forEach 不会等待
async function processItems(items) {
items.forEach(async (item) => {
await processItem(item); // 不会按预期等待
});
console.log('All done?'); // 会立即执行
}
// ✅ 正确做法 1:使用 for...of
async function processItems(items) {
for (const item of items) {
await processItem(item); // 会等待
}
console.log('All done!'); // 所有处理完成后才执行
}
// ✅ 正确做法 2:使用 Promise.all(如果可以并行)
async function processItems(items) {
await Promise.all(items.map(item => processItem(item)));
console.log('All done!');
}
为什么 forEach 不工作?
因为 forEach 的回调函数返回值会被忽略,即使你返回了 Promise,forEach 也不会等待它。
陷阱 2:Promise 构造函数中的错误
// 环境:浏览器 / Node.js 18+
// 场景:Promise 构造函数中的错误处理
// ❌ 错误会被自动捕获
const promise1 = new Promise((resolve, reject) => {
throw new Error('Error in executor');
});
promise1.catch(error => {
console.log('Caught:', error.message); // 会捕获到
});
// ⚠️ 但异步错误不会
const promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
throw new Error('Async error'); // 不会被 catch 捕获!
}, 100);
});
promise2.catch(error => {
console.log('Caught:', error.message); // 不会执行
});
// ✅ 正确做法:显式 reject
const promise3 = new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error('Async error')); // 会被捕获
}, 100);
});
陷阱 3:async 函数中的返回值
// 环境:浏览器 / Node.js 18+
// 场景:async 函数的返回值
async function test1() {
return 'hello'; // 返回 Promise.resolve('hello')
}
async function test2() {
return Promise.resolve('hello'); // 返回 Promise.resolve('hello')
}
async function test3() {
throw new Error('error'); // 返回 Promise.reject(Error)
}
// 所有 async 函数都返回 Promise
test1().then(console.log); // 'hello'
test2().then(console.log); // 'hello'
test3().catch(console.error); // Error: error
手写实现:理解原理的最佳方式
手写简化版 Promise
为了深入理解 Promise 的原理,我尝试手写了一个简化版的实现:
// 环境:浏览器 / Node.js 18+
// 场景:手写简化版 Promise(不完全符合 Promise/A+ 规范)
class SimplePromise {
constructor(executor) {
this.state = 'pending';
this.value = undefined;
this.reason = undefined;
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
const resolve = (value) => {
if (this.state === 'pending') {
this.state = 'fulfilled';
this.value = value;
this.onFulfilledCallbacks.forEach(fn => fn());
}
};
const reject = (reason) => {
if (this.state === 'pending') {
this.state = 'rejected';
this.reason = reason;
this.onRejectedCallbacks.forEach(fn => fn());
}
};
try {
executor(resolve, reject);
} catch (error) {
reject(error);
}
}
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason };
const promise2 = new SimplePromise((resolve, reject) => {
if (this.state === 'fulfilled') {
setTimeout(() => {
try {
const x = onFulfilled(this.value);
resolve(x);
} catch (error) {
reject(error);
}
}, 0);
}
if (this.state === 'rejected') {
setTimeout(() => {
try {
const x = onRejected(this.reason);
resolve(x);
} catch (error) {
reject(error);
}
}, 0);
}
if (this.state === 'pending') {
this.onFulfilledCallbacks.push(() => {
setTimeout(() => {
try {
const x = onFulfilled(this.value);
resolve(x);
} catch (error) {
reject(error);
}
}, 0);
});
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
const x = onRejected(this.reason);
resolve(x);
} catch (error) {
reject(error);
}
}, 0);
});
}
});
return promise2;
}
catch(onRejected) {
return this.then(null, onRejected);
}
}
// 测试
const p = new SimplePromise((resolve, reject) => {
setTimeout(() => resolve('Success!'), 1000);
});
p.then(value => {
console.log(value); // 'Success!'
return value + ' Again';
}).then(value => {
console.log(value); // 'Success! Again'
});
这个实现虽然简化了很多细节,但展示了 Promise 的核心机制:状态管理、回调队列、链式调用。
手写 Promise.all
// 环境:浏览器 / Node.js 18+
// 场景:手写 Promise.all
function promiseAll(promises) {
return new Promise((resolve, reject) => {
// 边界情况:空数组
if (!Array.isArray(promises)) {
return reject(new TypeError('Argument must be an array'));
}
if (promises.length === 0) {
return resolve([]);
}
const results = [];
let completedCount = 0;
promises.forEach((promise, index) => {
// 将值包装成 Promise(处理非 Promise 值)
Promise.resolve(promise)
.then(value => {
results[index] = value;
completedCount++;
// 所有都完成时 resolve
if (completedCount === promises.length) {
resolve(results);
}
})
.catch(error => {
// 任何一个失败就 reject
reject(error);
});
});
});
}
// 测试
promiseAll([
Promise.resolve(1),
Promise.resolve(2),
Promise.resolve(3)
]).then(results => {
console.log(results); // [1, 2, 3]
});
设计思想与演进
从 Callback 到 Promise:控制权的反转
回调函数的问题在于"控制反转"(Inversion of Control):你把控制权交给了第三方代码,无法保证回调会如何被调用。
Promise 通过"反转回控制反转"解决了这个问题:
// Callback:你把控制权交出去
doSomethingAsync(data, (error, result) => {
// 你不知道这个回调什么时候被调用、会被调用几次
});
// Promise:你保留控制权
const promise = doSomethingAsync(data);
promise.then(result => {
// 你决定何时、如何处理结果
});
从 Promise 到 async/await:语法的革新
Promise 虽然解决了回调地狱,但链式调用仍然不够直观。async/await 让异步代码看起来像同步代码,降低了认知负担。
// Promise 链式调用
function loadData() {
return fetchUser()
.then(user => fetchPosts(user.id))
.then(posts => fetchComments(posts[0].id))
.then(comments => {
return { comments };
});
}
// async/await
async function loadData() {
const user = await fetchUser();
const posts = await fetchPosts(user.id);
const comments = await fetchComments(posts[0].id);
return { comments };
}
这种演进体现了一个设计哲学:让复杂的事情看起来简单。
函数式编程的影响
Promise 的设计受到函数式编程的深刻影响:
- 不可变性:Promise 一旦确定状态就不会改变
- 可组合性:Promise 可以通过
.then()链式组合 - 声明式编程:关注"做什么"而不是"怎么做"
这些思想让异步代码更容易推理、测试和维护。
延伸与思考
在 AI 辅助编程时代,理解 Promise 还重要吗?
这个问题我在上一篇 Event Loop 文章中也思考过。经过实践,我发现:
理解 Promise 让你能更好地使用 AI:
-
判断 AI 生成代码的正确性:如果你不理解 Promise,如何判断 AI 给出的异步方案是否合理?
-
提出更精准的问题:
- 含糊:❌ "这个 Promise 有问题"
- 精准:✅ "为什么这个 Promise 链中的错误没有被 catch 捕获?"
-
性能优化的决策:AI 可能给出串行方案,但你需要判断是否应该改成并行。
一个实际例子:
AI 可能给你这样的代码:
// AI 生成的代码
async function loadAllData(ids) {
const results = [];
for (const id of ids) {
const data = await fetchData(id); // 串行执行
results.push(data);
}
return results;
}
如果你理解 Promise,就会知道可以优化成:
// 优化后的并行版本
async function loadAllData(ids) {
return Promise.all(ids.map(id => fetchData(id)));
}
未来的异步方案
JavaScript 的异步方案还在继续演进:
- Top-level await:在模块顶层直接使用 await
- AbortController:可取消的 Promise
- Async Iterator:异步数据流的处理
这些新特性进一步简化了异步编程,但它们都建立在 Promise 的基础之上。
待探索的问题
在研究 Promise 的过程中,我产生了一些新的疑问:
- Promise 和 Observable 的关系是什么? RxJS 的 Observable 似乎更强大,为什么 Promise 仍然是主流?
- 在 React 中如何优雅地处理 Promise? useEffect 中不能直接用 async,该如何处理?
- Worker 线程中的 Promise 如何与主线程通信? 跨线程的异步应该如何设计?
- 如何实现真正可取消的 Promise? 标准的 Promise 无法取消,实际项目中如何解决?
小结
Promise 和 async/await 不仅仅是解决异步问题的工具,它们代表了一种编程范式的转变:从命令式到声明式,从回调地狱到优雅的线性代码。
通过这次学习,我对异步编程有了更深的理解:
- Promise 解决的不只是语法问题,更是控制权、错误处理、可组合性的问题
- async/await 的本质是语法糖,理解它如何转换成 Promise 帮助我们理解 Event Loop
- 选择串行还是并行,取决于业务逻辑而不是语法习惯
- 错误处理需要统筹考虑,而不是到处 try-catch
这篇文章是我的学习笔记,而非权威教程。如果你有不同的理解或补充,欢迎交流讨论。
最后留一个开放性问题:在你的实际开发中,遇到过哪些 Promise 相关的坑?你是如何解决的?
参考资料
- MDN - Promise - Promise 官方文档
- MDN - async function - async/await 官方文档
- Promises/A+ 规范 - Promise 的标准规范
- You Don't Know JS: Async & Performance - Kyle Simpson 关于异步的深度讲解
- JavaScript Visualized: Promises & Async/Await - 可视化教程