JavaScript Generator函数与async/await用法详解
本文详细介绍JavaScript中Generator函数的定义、执行机制及next/throw/return方法的使用,并结合async/await语法探讨异步编程的演进。通过对比Generator与async函数的特性,解析自动执行器的实现原理及性能优化建议,帮助开发者深入理解JS异步处理的核心概念。
Generator函数
Generator 函数在语法结构上看似与普通函数不同,但其本质仍属于常规函数范畴,主要具备两个显著特征。
1、function关键字与函数名之间必须包含一个星号(*)。
2、函数体内部需使用yield表达式来定义不同的内部状态(yield在英语中意为“产出”),该关键字具有暂停执行和等待后续调用的语义。
调用Generator函数后,其执行行为与普通函数类似,但当代码执行至 yield 关键字时会暂停(暂停),必须等待调用 next 方法后才会继续(继续)执行后续代码,直至再次遇到 yield 或 return 关键字。
当 yield 作为 表达式 表达式的一部分使用时,必须使用 () 将其括起来,否则会导致语法错误,例如代码:2 * (yield x)。
调用 next 方法会指示 generator 函数从当前位置继续往后执行语句,默认执行逻辑会到达下一个 下一个 yield 语句,并将 yield 后的内容 包装在返回对象的 value 属性中。若未遇到 return 或 函数结束,则返回对象中的 done 属性值为 false;若遇到终止条件,done 属性值则为 true。
return 方法用于标识函数的结束,其返回的内容同样可以通过 next 方法获取;此外,若函数中未显式编写 return,其行为与 return undefined 类似。
可参考以下代码案例进行理解。
function* generator() {
yield 1
yield 2
yield 3
yield 4
return 5 // { value: 5, done: true },再往后 next 就和不写一样了
// 不写return,则与return undefined 一样 返回 { value: undefined, done: true }
}
//获取 generator 遍历器
let g = generator()
let next = null
do {
next = g.next() //开始往后执行到下一个 yield 语句,并返回
console.log(next)
}while(next?.done === false) //执行到 return 或 函数结束 则停止打印
next = g.next()
console.log(next)
// { value: 1, done: false }
// { value: 2, done: false }
// { value: 3, done: false }
// { value: 4, done: false }
// 有return 5 打印 { value: 5, done: true }, 没有则打印 { value: undefined, done: true }若在 class 类中使用,表示方式略有不同。由于类方法默认带有构造函数包装,只需在方法名前添加一个 * 即可实现相同功能。
class A {
//前面加上 * 即可
* generator() {
yield 1
yield 2
}
}
let a = new A()
let g = a.generator()
let next = g.next()
console.log(next)
next = g.next()
console.log(next)generator 与 Iterator遍历器
从上述打印结果可以看出,generator函数 实际上返回的是一个 Iterator遍历器 对象,因此我们也可以利用遍历器的机制来执行 generator遍历器。
下面演示如何使用 for ... of 语法来遍历上述生成的 generator遍历器。
//从上面就可以看出,generator函数 返回了一个 Iterator遍历器对象
//我们遍历一次试试
let g = generator()
for (let item of g) {
console.log(item)
}
//结果打印 1 2 3 4观察运行结果发现,最后定义的 return 并未被打印输出。了解遍历器机制可知,当遍历器遇到 done 属性值为 true 时会直接终止遍历(因此,若将 return 语句 用于存放遍历内容,需注意此限制)。
我们再对比验证一下,确认我们的 generator遍历器 是否确实属于 Iteraotr遍历器,结果显示两者 一模一样。
//g[Symbol.iterator]() === g //遍历器属性一致,返回为 true,不信打印遍历器属性比较试试next
前文已介绍,调用 generator遍历器 的 next() 方法后,执行器会继续运行至下一个 yield语句。
此外,next 方法支持 传参 参数,我们传入的 传递的参数 会作为 上一个 yield 返回的结果 的值返回;若未传递参数,则默认返回 undefined。
注意:yield 返回的结果 不是 该表达式后面的内容,且当 yield 作为表达式一项时,需要使用 () 括起来,否则会报错。
通过以下案例即可直观看到结果(执行过程已在代码中标注)。
//包含表达式时,yield 语句需要用括号括起来,否则会报错
function* generator() {
let num1 = yield 1
console.log('num1', num1)
let num2 = num1 * (yield 2)
console.log('num2', num2)
yield 3
yield 4
}
//next传入的值,作为上一个 yield 的返回值使用
let g = generator()
let next = g.next() //此时执行到 yield 1,并包装返回 yield 1 执行的结果
console.log(next) //{ value: 1, done: false }
//传入 2, 当做上一个语句的 yield 1 的返回值(不传接收到的值均为undefined)
//即:返回 2 赋值给 num,然后执行到 (yield 2),并包装返回 (yield 2) 的结果
next = g.next(2) //执行完毕后,打印:num1 2
console.log(next) //{ value: 2, done: false }
//传入 3. 当做上一个语句的 (yield 2) 的返回值(不传接收到的值均为undefined)
//即:num2 = num1 * 3, 然后执行到 yield 3,并包装返回 yield 3 的结果
next = g.next(3) //执行完毕后,打印:num2 6
console.log(next) //{ value: 3, done: false }由上述分析可见,next 传参 功能十分实用,除上述用法外,甚至可以利用传递的参数来跳出死循环等场景。
throw
此处的 throw 是指通过 generator 遍历器抛出的异常,该异常会抛出到 generator 函数里面 内部,因此需要注意控制抛出 异常的位置 的位置,以便更好地配合 try...catch 结构使用。
//throw()
// 相当于在 generator 里面抛出了一个异常
function* generator() {
try {
yield 1
}catch(err) {
console.log(err)
}
yield 2
yield 3
yield 4
}
let g = generator()
let next = g.next()
console.log(next)
//执行完毕 yield 1后,随后在 yield 1 ~ yield 2 之间抛出异常,因此,try...catch 包裹主 yield 1 才行
let t = g.throw('啦啦啦') // 抛出异常并执行到下一个 yield,如果没处理错误,程序异常
console.log(t)
next = g.next()
console.log(next)return
指定调用 return 会立即结束 generator 函数 的执行,并返回 { value: undefined, done: true },前文已有介绍。
function* generator() {
yield 1
yield 2
yield 3
yield 4
}
let g = generator()
next = g.next()
next = g.return() //直接返回 { value: undefined, done: true },如果传参则返回return传递的参数
console.log(next) //会直接结束另外,当存在 try...finally 代码块且执行流进入 try 部分时,return 操作会触发 仍然执行finally finally 块内的语句,并执行至 finally里面 中的 yield 语句;finally 执行完毕后,才是真正的函数结束。此时,return 带入的参数会应用到 finally 语句的 末尾 末尾。
function* generator() {
yield 1
try {
//执行到这里,外面 return 的话,也需要走完 finally 才会结束
yield 2
yield 3
}finally {
yield 7
yield 8
}
yield 4
}
let g1 = generator()
next = g1.next() // { value: 1, done: false }
next = g1.next() // { value: 2, done: false }
next = g1.return(100) // { value: 7, done: false },return 后返回下一个,为finally中的 yield 7,这里顺道返回一个值测试一下最后的返回值
next = g1.next() // { value: 8, done: false }
next = g1.next() // { value: 100, done: true }
next = g1.next() // { value: undefined, done: true }yield*
yield* 看似一个指针,其指向一个 generator遍历器,会自动展开嵌套的生成器,此处不再赘述。
//yield* 会展开 generator 函数
function* gen1() {
yield 1
yield 2
}
function* gen2() {
yield 0
yield* gen1()
}
//相当于下面展开的语句,可以看出yield语句总是一个一个执行的,即使嵌套也不会一次执行一堆
function* gen3() {
yield 0
yield 1
yield 2
}generator应用
generator 的应用场景广泛,此处仅简要介绍两种基础用法,以此略作尝试。
正常切换开关通常需要维护状态,此处通过 generator 遍历器避免了新增开关状态的需求(注:此方法不适用于多方控制场景,例如需要与后台远程同步状态的情况)。
//切换状态
function* toggleSwitch() {
while(true) {
console.log('开灯')
yield 1
console.log('关灯')
yield 0
}
}
//是不是切换很简单了
let t = toggleSwitch()
let next = t.next() //开灯
console.log(next)
next = t.next() //关灯
console.log(next)流程化管理中,外部调用者无需关心内部逻辑(例如:加工软件中,不同工种只需关注自己的操作工序,完成后点击一次 next 即可推进流程)。
function* order() {
yield '收到订单'
yield '加工'
yield '送检'
yield '质检'
yield '发货'
yield '完成订单'
}
let o = order()
let next = o.next() //收到订单
next = o.next() //加工
next = o.next() //送检
next = o.next() //质检
next = o.next() //发货
next = o.next() //完成订单async、await函数
async、await 是我们日常开发中较为常用的语法,此处作简要介绍。
async、await 是基于 generator函数 改造而成的,它改进了 generator函数 作为普通函数时的痛点。其内部加入了 执行器,使得函数能像普通函数一样直接执行,更加 方便,且 语义更加清晰 语义更加清晰,最终结果返回一个 Promise(可以看出其内部使用了Promise进行包装)。
简而言之:async 用于声明一个异步函数,await 用于等待执行,当 await 语句执行完毕后,才会执行后续代码(若出现错误则直接抛出异常并结束执行,不再向后继续)。此外,如果没有 await 的存在,async 函数的执行顺序与普通同步函数并无区别。
ps:关于 promise 前文已有介绍,await 的执行过程与其类似,实际上会立即将任务加入队列,待前面的任务执行完毕后,才会执行 await 后面的语句。
简单做一个使用案例,
async function a() {
await promise1...
await promise2...
// 如果不return自己内容,返回的则是 undefind
return 1
}
function cc() {
a().then(res => {
console.log(res)
}).catch(err => {
console.log(err)
})
}你可能会想,如果第一个 promise 出错了怎么办,第二个会执行么?
答案:不会,第一个出错后,会抛出一个异常,此时会结束解释器的执行,直接反馈错误到外部。
下面改进上一个案例
function a1() {
return Promise.reject('出错了 ')
}
function a2() {
return Promise.resolve('成功了 ')
}
async function a() {
await a1()
console.log(11111111111)
await a2()
// 如果不return自己内容,返回的则是 undefind
return 1
}
function cc() {
a().then(res => {
console.log(res)
}).catch(err => {
console.log(err)
})
}
//结果是没有执行打印 11111111111,且执行到了 .catch 中下面我们模拟传递多个图片的案例,可以看出 async、await 应用多么方便。
function uploadAImage(fileUrl) {
return new Promise(function(resolve, reject) {
setTimeout(() => {
let res = fileUrl + '成功了'
console.log(res)
resolve(res)
}, 500);
})
}
async function uploadImages() {
//准备多个图片url
let fileUrl = ['url1', 'url2', 'url3']
for (url of fileUrl) {
try {
await uploadAImage(url)
}catch(err) {
console.log(err)
//实际上更复杂,成功的下次就不需要在上传了
return Promise.reject('存在上传失败的内容')
}
}
return '都成功了'
}
uploadImages().then(res => {
console.log(res)
}).catch(err => {
console.log(err)
})async、await 函数使用简单,就先介绍到这里了,后续会对 generator 进行对比,解析其是如何被改造的。
generator 模仿 async 自动执行
前文提到 async 函数 是通过 generator 函数 改造而来的,内部添加了执行器,我们自己尝试编写一个简易的 generator 执行器。
这里是一个async 的默认执行案例
//使用 generator + 执行器,简单翻译一下 async、await
async function asyncDefaultFunction() {
let res = await new Promise((resolve) => {
setTimeout(() => {
resolve(1)
}, 1000);
// setTimeout(() => {
// reject('啦啦啦')
// }, 1000);
})
return res
}
asyncDefaultFunction().then(res => {
console.log(res)
})我们使用 generator 改造成 async 自动执行的模式
//使用 async 之后,实际上我们将返回函数包装一下即可,并且将 yield 就是 await
function asyncFunction() {
return co(function* () {
//async 函数里面的内容,将 yield 与 await 替换即可
let res = yield new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1)
}, 1000);
// setTimeout(() => {
// reject('啦啦啦')
// }, 1000);
})
return res
})
}
//编写一个自动执行 generator 的 co 函数
function co(genFunc) {
return new Promise(function (resolve, reject) {
const g = genFunc() //获取generator,准备执行
function nextFunc(value) {
let next;
try {
next = g.next(value)
}catch(err) {
return reject(err)
}
if (next.done) {
return resolve(next.value)
}
//为什么要promise包装,语句可能为同步函数,可能为异步函数,木事保证正确执行
//如果为 promise 直接原封不动返回,如果为普通对象则包装,具体见promise
Promise.resolve(next.value).then(function(res) {
nextFunc(res)
}, function(err) {
reject(err)
})
}
nextFunc(undefined)
})
}
asyncFunction().then(res => {
console.log(res)
}).catch(err => {
console.log(err)
})这样就实现了一个简易的 generator 自动执行器
同时我们也发现了,async 与我们普通函数相比,加剧了 负担,为了优化性能,我们平时如果没有用到异步函数(或者连 await 都用不到的),将多余的 async 去掉吧,这样某种程度上能够避免性能浪费,尤其是到了循环语句。
最后
看到了这里,相信我们也能学习到不少东西,可以想一下,其他语言是不是也是类似这样呢,遇到了相似的情况我们是不是也豁然开朗了呢,很多语言是互通的,学习就是积累的过程,希望我们此次能有所收获!
以上就是JS中Generator函数与async函数用法介绍的详细内容,更多关于JS Generator函数与async函数的资料请关注本站其它相关文章!
什么时候该用JS构造器:与工厂模式、字面量的选择对比
2026-11-05 16:18:22
推荐几款轻量级JS照片墙开源库及对比
2026-11-05 15:56:19
JavaScript递归写法有哪些常见陷阱及避免方法
2026-11-05 14:53:38
JS数组浅拷贝与深拷贝怎么选?场景与方法总结
2026-11-05 14:41:02
p5.js 中 setup() 和 draw() 函数的执行机制详解
2026-11-05 12:32:38
