Promise 通过链式调用改善了回调地狱的问题,但面对复杂异步流程时,Promise 仍存在提升空间。本文是在对事件循环、Promise 探讨的基础上,聚焦于更高层抽象工具 Generator 和 Async/Await 的讲解,帮助开发者更全面地掌握 JavaScript 异步编程。
一、Generator 函数
为何需要 Generator 函数
在异步编程中,复杂的异步流程需要更灵活的控制方式。普通函数一旦开始执行就会一直运行到结束,无法暂停和恢复,对于异步操作的中间过程难以进行精细的控制,难以满足异步任务分步执行、按需生成数据等需求。Generator 函数提供了一种可中断和恢复执行的机制,为异步编程带来了新的思路。
定义方式
Generator 函数通过在function
关键字后添加*
来定义,例如:
javascript
function* myGenerator() {
// 函数体
}
这里的myGenerator
就是一个 Generator 函数。
function*
这种特殊的声明方式告诉 JavaScript 引擎,该函数具有可暂停和恢复执行的特殊行为。- 与普通函数定义
function myFunction() {... }
相比,*
是 Generator 函数的标志性符号。
yield 关键字的关键作用
暂停执行与返回值 :Generator 函数在执行过程中,一旦遇到 yield
,其执行流程就会在此处中断。
-
yield
右侧的值作为本次调用next()
返回对象的value
属性,是 Generator 函数向外部输出数据的方式。 -
yield
表达式自身的返回值由下一次next()
传入的参数决定,是外部代码向 Generator 函数内部输入数据的方式。
恢复执行与数据交互: 当 Generator 函数被调用时,会返回一个迭代器对象。该迭代器对象包含以下内容:
-
next
方法 :用于控制 Generator 函数的执行。-
每次调用该方法,都会返回一个包含
value
和done
属性的对象。value
属性表示当前yield
或return
语句所产出的值,done
属性则是一个布尔值,用于指示 Generator 函数是否执行完毕。 -
每次调用
next
方法,Generator 函数就会从上次暂停的位置(首次调用则从开始位置)继续执行,直至遇到下一个yield
语句或者return
语句。 -
遇到
yield
语句时,函数暂停执行,next
方法返回的对象中,value
为yield
右侧表达式的值,done
为false
。 -
遇到
return
语句时,函数执行完毕,next
方法返回的对象中,value
为return
后面表达式的值,done
为true
。
-
-
done
属性 :用于标识 Generator 函数的执行状态。- 当 Generator 函数执行到
return
语句,或者执行到函数末尾且没有更多yield
语句时,done
属性值为true
;否则为false
。
- 当 Generator 函数执行到
如果next
方法传递了参数,这个参数会作为上一个yield
语句的返回值,实现 Generator 函数与外部调用者之间的数据交互。
javascript
function* fruitGenerator() {
let firstFruit = yield '请选择第一种水果';
let secondFruit = yield `你选择了 ${firstFruit},请选择第二种水果`;
return `你选择的水果组合是 ${firstFruit} 和 ${secondFruit}`;
}
let fruitGen = fruitGenerator();
let firstPrompt = fruitGen.next();
console.log(firstPrompt.value);
let firstChoice = fruitGen.next('苹果');
console.log(firstChoice.value);
let secondChoice = fruitGen.next('香蕉');
console.log(secondChoice.value);

-
创建 Generator 对象:
- 调用
fruitGenerator()
函数并不会立即执行函数体中的代码,而是返回一个 Generator 对象fruitGen
。这个对象可以用来控制 Generator 函数的执行。
- 调用
-
第一次调用
next
方法:-
当调用
fruitGen.next()
时,fruitGenerator
函数开始执行。 -
函数执行到
yield '请选择第一种水果'
时暂停,yield
右侧的字符串'请选择第一种水果'
会作为这次next()
方法返回对象的value
属性值。 -
firstPrompt
接收next()
方法的返回值,它是一个对象,格式为{ value: '请选择第一种水果', done: false }
。
-
-
第二次调用
next
方法 :让函数从暂停处继续执行,next()
传入的'苹果'
就作为上一个yield
表达式(即yield '请选择第一种水果'
)的返回值,这个返回值就会赋给firstFruit
。 -
第三次调用
next
方法 : 函数从第二个yield
处继续执行,'香蕉'
作为上一个yield
返回值赋给secondFruit
,函数执行return
语句,返回'你选择的水果组合是 苹果 和 香蕉'
。
Generator 函数与普通函数在执行上的本质区别
普通函数的执行模式 :普通函数一旦被调用,就会从函数的第一行代码开始,按照顺序依次执行,直到遇到return
语句或者函数体结束。在执行过程中,它不会暂停,也无法在中途将执行权交还给调用者,并且其内部的变量作用域和状态在函数执行结束后就会被销毁(除了闭包情况)。例如:
javascript
function add(a, b) {
let result = a + b;
return result;
}
let sum = add(2, 3);
console.log(sum);
add
函数被调用后,立即计算a + b
,然后返回结果,整个过程一气呵成,没有中间暂停的可能。
Generator 函数的执行模式 :Generator 函数在调用后,并不会立即执行函数体中的代码,而是返回一个迭代器对象。当调用这个迭代器对象的next()
方法时,Generator 函数才开始执行,直到遇到yield
语句。
应用场景
异步任务分步执行
在处理异步操作时,Generator 函数可以将异步任务分成多个步骤,每个步骤通过yield
暂停,等待异步操作完成后再通过next()
方法恢复执行,使异步代码看起来更像同步代码。
- 复杂异步流程控制:当有多个异步操作,且它们之间存在顺序依赖关系时,比如先读取配置文件,再根据配置信息建立数据库连接,最后查询数据库。
- 异步操作并发限制 :有些场景下,需要限制同时进行的异步操作数量,以避免资源过度消耗。例如,同时向多个服务器发送请求获取数据,但服务器对并发请求数有限制。
- 使用 Generator 函数可以每次
yield
一个请求操作,控制并发数量,当一个请求完成后,再通过next
方法发送下一个请求。
- 使用 Generator 函数可以每次
- 处理异步操作结果的中间过程 :如果在异步操作过程中,需要对中间结果进行处理或转换,然后再基于处理后的结果进行下一步异步操作,分步执行就很有必要。
- 比如先从 API 获取原始数据,然后对数据进行清洗和格式化,再将处理后的数据保存到数据库中。通过 Generator 函数,可以方便地在
yield
之间插入对中间结果的处理逻辑。
- 比如先从 API 获取原始数据,然后对数据进行清洗和格式化,再将处理后的数据保存到数据库中。通过 Generator 函数,可以方便地在
- 用户交互与异步操作结合 :在与用户交互的场景中,可能需要根据用户的输入或操作来决定下一步的异步操作。
- 例如,在一个表单提交过程中,先验证用户输入的合法性(异步操作),如果合法,再根据用户选择的不同选项执行不同的异步保存操作。
惰性生成序列值
Generator 函数可以按需生成一系列的值,非常适合生成斐波那契数列、无限数据流等场景。以生成斐波那契数列为例:
javascript
function* fibonacci() {
let a = 0, b = 1;
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
let fib = fibonacci();
console.log(fib.next().value);
fibonacci
函数通过yield
按需生成斐波那契数列的每一项,避免一次性生成大量数据,节省内存。
二、Async/Await
为何需要 Async/Await
尽管 Promise 在一定程度上改善了异步编程体验,但链式调用在处理复杂异步流程时仍显繁琐。Async/Await 语法基于 Promise 和 Generator 进一步简化异步代码,让开发者可以用更接近同步代码的方式编写异步逻辑,同时优化了错误处理机制。
Async/Await 定义与使用
async
函数语法格式
async
函数用于定义一个异步函数,它的定义方式和普通函数类似,不过要在function
关键字前面加上async
关键字。其基本语法如下:
javascript
// 函数声明方式
async function functionName(parameters) {
// 函数体
return value;
}
// 函数表达式方式
const functionName = async function(parameters) {
// 函数体
return value;
};
// 箭头函数方式
const functionName = async (parameters) => {
// 函数体
return value;
};
await
关键字用法
await
关键字只能在 async
函数内部使用,它的作用是暂停 async
函数的执行,等待其后跟随的 Promise
对象被解决(resolved)或被拒绝(rejected)。
- 当
Promise
被解决时,await
表达式会返回Promise
的解决值。 - 当
Promise
被拒绝时,await
表达式会抛出异常。
javascript
function asyncOperation() {
return new Promise((resolve) => {
setTimeout(() => {
resolve('操作完成');
}, 1000);
});
}
async function main() {
try {
// await 暂停执行,等待 asyncOperation 返回的 Promise 被解决
let result = await asyncOperation();
console.log(result); // 输出: 操作完成
} catch (error) {
console.error('操作出错:', error.message);
}
}
main();
asyncOperation
函数返回一个 Promise
,await asyncOperation()
会暂停 main
函数的执行,直到 asyncOperation
返回的 Promise
被解决,然后将解决值赋给 result
变量并继续执行后续代码。如果 Promise
被拒绝,await
会抛出异常,被 try...catch
捕获。
返回值特性
-
async
函数会返回一个Promise
对象。 -
返回值包装:
-
成功返回非
Promise
值 :若函数内使用return
语句返回一个非Promise
的值,JavaScript 会自动把这个值包装成一个已解决(resolved)状态的Promise
对象,其value
属性就是返回的值。- 这意味着当调用这个
async
函数时,返回的Promise
对象会立即进入已解决状态,并且可以通过.then()
方法获取到返回的值。
- 这意味着当调用这个
-
成功返回
Promise
值 :若返回一个Promise
对象,那么async
函数返回的就是这个Promise
对象本身。- 该
Promise
对象的状态和结果取决于返回的Promise
的执行情况。如果这个Promise
被解决,async
函数返回的Promise
也会进入已解决状态;如果被拒绝,则进入被拒绝状态。
- 该
-
抛出异常 :若函数内部抛出异常,返回的
Promise
对象会处于被拒绝(rejected)状态,异常信息会作为Promise
的拒绝原因。可以通过.catch()
方法捕获这个异常信息。
-
示例 1:返回非 Promise
值
js
async function sayHello() {
return "Hello, World!";
}
// 调用 async 函数
const promise = sayHello();
// 打印返回的 Promise 对象
console.log(promise);
// 使用 .then 方法处理 Promise 的结果
promise.then((message) => {
console.log(message);
});

示例 2:返回 Promise
值
js
function asyncOperation() {
return new Promise((resolve) => {
setTimeout(() => {
resolve('操作完成');
}, 1000);
});
}
async function returnPromise() {
return asyncOperation();
}
(async () => {
let result = await returnPromise();
console.log(result);
})();
Async/Await 与 Generator、Promise 的关系
- Async/Await 本质上是基于 Promise 和 Generator 的语法糖。它借助 Promise 处理异步操作的状态和结果,利用 Generator 函数可暂停恢复执行的特性,通过自动执行器实现无需手动调用
next()
的效果。 - 与 Generator 函数相比,Async/Await 内置了错误冒泡机制,使得错误处理更加便捷。在
async
函数中,可直接使用try...catch
捕获并处理await
表达式抛出的异常,而无需像 Generator 函数那样在每次调用next()
时手动检查错误。
Async/Await 的优势
代码更简洁易读
- 普通
Promise
:在处理多个异步操作时,Promise
通过链式调用.then () 来处理异步操作的结果,当有多个异步操作依赖时,代码会形成多层嵌套,可读性差。
javascript
getData()
.then(result1 => {
return getAnotherData(result1.id)
.then(result2 => {
return getThirdData(result2.name)
.then(result3 => {
console.log(result3);
});
});
});
async/await
:async/await
使异步代码看起来更像同步代码,通过await
暂停异步函数的执行,等待Promise
被解决,让代码更清晰直观。
javascript
async function getDataAndLog() {
const result1 = await getData();
const result2 = await getAnotherData(result1.id);
const result3 = await getThirdData(result2.name);
console.log(result3);
}
getDataAndLog();
错误处理更方便
- 普通
Promise
:在Promise
链中,错误处理需要在每个.then()
方法中添加.catch()
,或者在链的末尾统一添加.catch()
,但如果中间某个.then()
方法中返回了一个新的Promise
且没有正确处理错误,可能会导致错误被忽略。
javascript
getData()
.then(result1 => {
// 这里可能会抛出错误,但没有及时处理
return getAnotherData(result1.id);
})
.catch(error => {
console.error('全局错误处理:', error);
});
async/await
:async/await
可以使用try...catch
块来捕获错误,更加直观和方便,错误处理代码更集中。
javascript
async function getDataAndLog() {
try {
const result1 = await getData();
const result2 = await getAnotherData(result1.id);
console.log(result2);
} catch (error) {
console.error('错误处理:', error);
}
}
getDataAndLog();
Async/Await 的应用
表单提交与验证
- 在处理表单提交时,通常需要验证用户输入,并在验证通过后将数据发送到服务器。
async/await
可以将验证和提交操作异步化,使代码更有条理。例如:
javascript
async function submitForm(formData) {
// 验证表单数据
if (!formData.email.match(/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}$/)) {
throw new Error('无效的电子邮件地址');
}
// 发送表单数据到服务器
const response = await fetch('https://example.com/api/submit-form', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(formData)
});
if (!response.ok) {
throw new Error('表单提交失败');
}
return await response.json();
}
const formData = { email: '[email protected]', password: '123456' };
submitForm(formData).then(result => console.log(result));
数据获取与处理
- 当从服务器获取数据并需要对数据进行进一步处理时,
async/await
可以让代码更清晰。例如,从 API 获取用户信息,然后根据用户信息获取用户的订单列表:
javascript
async function getUserOrders() {
// 获取用户信息
const userResponse = await fetch('https://example.com/api/user');
const userData = await userResponse.json();
// 根据用户ID获取订单列表
const ordersResponse = await fetch(`https://example.com/api/orders/${userData.id}`);
const ordersData = await ordersResponse.json();
return ordersData;
}
getUserOrders().then(result => console.log(result));