作为一名长期奋战在前端开发一线的工程师,我见证了JavaScript异步编程从回调地狱到Promise再到Generator和async/await的完整演进历程。记得2015年刚接触ES6时,第一次看到Generator函数那种星号(*)语法时的困惑,到后来async/await彻底改变了我的编码方式。本文将结合我多年实战经验,带你彻底掌握这两种革命性的异步编程方案。
在ES6之前,我们处理异步主要依赖四种方式:
特别提示:虽然Promise通过链式调用改善了回调地狱,但then的连续调用仍然让代码显得冗长,特别是当需要处理多个异步操作时,代码可读性会急剧下降。
Generator函数是ES6引入的一种特殊函数,它可以通过yield关键字暂停函数执行,并在需要时恢复执行。这种"暂停-继续"的机制为异步编程提供了全新的思路。
定义Generator函数需要在function关键字后添加星号:
javascript复制function* myGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
与普通函数不同,调用Generator函数不会立即执行函数体,而是返回一个迭代器对象:
javascript复制const gen = myGenerator();
Generator函数的执行完全由迭代器控制,每次调用next()方法都会执行到下一个yield表达式或函数结束。让我们通过一个完整示例来理解:
javascript复制function* countDown(from) {
while(from > 0) {
yield from;
from--;
}
return "Lift off!";
}
const countdownGen = countDown(3);
console.log(countdownGen.next()); // {value: 3, done: false}
console.log(countdownGen.next()); // {value: 2, done: false}
console.log(countdownGen.next()); // {value: 1, done: false}
console.log(countdownGen.next()); // {value: "Lift off!", done: true}
关键执行特点:
Generator真正强大的地方在于它可以用来管理异步流程。下面是一个模拟异步操作的示例:
javascript复制function* asyncTask() {
const data1 = yield fetchData('/api/data1');
console.log('Data1 received:', data1);
const data2 = yield fetchData('/api/data2');
console.log('Data2 received:', data2);
return 'All data loaded';
}
function fetchData(url) {
return new Promise(resolve => {
setTimeout(() => {
resolve(`Data from ${url}`);
}, 1000);
});
}
// 手动执行Generator
const gen = asyncTask();
gen.next().value
.then(data1 => {
return gen.next(data1).value;
})
.then(data2 => {
const result = gen.next(data2);
console.log(result.value); // "All data loaded"
});
这个例子展示了如何使用Generator处理异步操作,但手动执行显然不够优雅。在实际开发中,我们通常会使用co这样的库来自动执行Generator函数。
虽然Generator提供了强大的异步控制能力,但它需要手动执行或依赖外部库。ES2017引入的async/await语法糖完美解决了这个问题。
将上面的Generator示例改写为async/await:
javascript复制async function asyncTask() {
const data1 = await fetchData('/api/data1');
console.log('Data1 received:', data1);
const data2 = await fetchData('/api/data2');
console.log('Data2 received:', data2);
return 'All data loaded';
}
// 调用方式
asyncTask().then(result => {
console.log(result); // "All data loaded"
});
可以看到,async/await让异步代码看起来几乎和同步代码一样简洁明了。
在async/await中,我们可以使用传统的try/catch来处理错误:
javascript复制async function fetchWithRetry(url, retries = 3) {
try {
const response = await fetch(url);
if (!response.ok) throw new Error('Network response was not ok');
return await response.json();
} catch (error) {
if (retries <= 0) throw error;
await new Promise(resolve => setTimeout(resolve, 1000));
return fetchWithRetry(url, retries - 1);
}
}
这个例子还展示了如何在async函数中实现重试逻辑,这是实际项目中非常实用的技巧。
虽然await让代码看起来是顺序执行的,但我们仍然可以实现并行操作:
javascript复制async function parallelTasks() {
// 同时启动两个异步任务
const [result1, result2] = await Promise.all([
fetchData('/api/data1'),
fetchData('/api/data2')
]);
// 处理结果
console.log(result1, result2);
}
在某些特殊场景下,我们可能需要混合使用Generator和async/await:
javascript复制async function* asyncGenerator() {
let i = 0;
while (true) {
await new Promise(resolve => setTimeout(resolve, 1000));
yield i++;
}
}
(async function() {
for await (const num of asyncGenerator()) {
console.log(num);
if (num > 5) break;
}
})();
这个例子创建了一个每秒产生一个数字的异步生成器,展示了如何结合使用for-await-of循环来处理异步数据流。
虽然async/await极大简化了异步编程,但也需要注意:
javascript复制async function process() {
const data = fetchData(); // 缺少await!
console.log(data); // 输出Promise对象而非实际数据
}
javascript复制async function slowOperation() {
const a = await getA(); // 可以并行执行
const b = await getB(); // 的两个独立操作
}
javascript复制async function riskyOperation() {
throw new Error('Something went wrong');
}
// 调用处没有catch处理
riskyOperation(); // 未处理的Promise拒绝
--inspect标志运行Node.js应用,配合Chrome DevTools调试async代码util.inspect或自定义函数打印完整的Promise状态javascript复制function withErrorHandling(fn) {
return async (...args) => {
try {
return await fn(...args);
} catch (error) {
console.error('Error:', error);
// 统一错误上报逻辑
reportError(error);
throw error; // 或者返回默认值
}
};
}
javascript复制async function fetchWithTimeout(url, options, timeout = 5000) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
try {
const response = await fetch(url, {
...options,
signal: controller.signal
});
clearTimeout(timeoutId);
return response;
} catch (error) {
clearTimeout(timeoutId);
throw error;
}
}
javascript复制async function trackedOperation() {
const start = performance.now();
try {
return await operation();
} finally {
const duration = performance.now() - start;
metrics.track('operation_duration', duration);
}
}
在实际项目中,我发现合理使用async/await可以显著提高代码可维护性,特别是在处理复杂业务逻辑时。一个典型的电商订单处理流程可能涉及库存检查、支付处理、物流安排等多个异步步骤,使用async/await可以让代码逻辑保持清晰。