ES6 Generator函数核心特性与异步编程实战详解
本文深入解析ES6 Generator函数的核心机制,包括其作为异步编程解决方案的原理。详细阐述yield与return的区别、参数传递、异常捕获及语法位置限制,并通过结合Promise与co模块的实例,展示如何利用同步方式书写异步逻辑,提升代码可读性。
Generator函数概述
本文通过具体实例解析ES6中Generator函数的应用方法,旨在为读者提供实用的参考与学习素材。
Generator函数是处理异步编程的一种有效方案。它本质上是一个普通函数,但具备两个显著特征:首先,在function关键字与函数名之间必须添加一个星号(*);其次,函数体内必须包含yield表达式。调用Generator函数时,不会立即执行函数体内的代码,而是返回一个遍历器对象(Iterator Object),该对象实现了Iterator接口,用于控制函数的分段执行。
Generator函数基础认知
理解Generator函数的基本结构与运行机制是掌握其用法的基础。
function * fn() {
console.log('hello');
return 'Joh';
}
// 执行fn()时不会直接执行方法体中的代码,它会返回一个指针, 这个指针实现了 interator接口,也就是返回一个interator对象
let it = fn();
// 通过调用next就会执行方法体, 返回结果是 {value:'Joh', done:true}, 其中next返回的是函数体中return的值
let res = it.next();
console.log(res);
yield与return的区别
在Generator函数中,yield表达式与return语句有着本质的区别。yield用于将函数分割为多个执行阶段,每个yield语句对应一个断点;而return仅能出现一次,用于指定函数的最终返回值。若没有return语句,done为true时的value值将为undefined。
function * fn() {
// 和yield相配合,把一个generator内部分为几个断点来执行,每个断点就是yield语句
// 注意 yield和return的区别: yield可以有多个,return只能有1个
yield 1;
yield 2;
yield 3;
return 4; // 可以没有return值,done为true的value将会是undefined
}
let it = fn();
// 在for-of 循环中只能打印done为false的value值,done为true时,程序终止
for(let v of it) {
console.log(v); // 分别输出 1 2 3
}
yield的值传递与赋值
yield表达式不仅用于暂停执行,还可以接收外部传入的值。当调用next()方法并传入参数时,该参数会成为上一个yield表达式的返回值。需要注意的是,只有当Generator函数执行到该yield点时,传入的参数才会生效;若函数已执行完毕,后续传入的参数将被忽略,value返回undefined。
function * fn(_name) {
let name = yield _name; // yield 的默认值为undefined
return name;
}
let it = fn('Joh');
console.log(it.next()); // {value:'Joh', done:false}
console.log(it.next('Tom')); // {value:'Tom', done:true} // 此处value应该为undefined,但是通过next参数的形式赋值改变了最后一个值
console.log(it.next('Lily')); // {value: undefined, done:true} // 已经循环完毕,即使传值也是undefined
yield语句位置与语法细节
yield语句在代码中的位置及括号使用需遵循特定规则。例如,在表达式中使用yield时,若其参与运算或作为函数参数,可能需要使用括号明确优先级,否则可能导致语法错误或逻辑偏差。
function sum(a, b) {
return a + b;
}
function * fn() {
let res = sum(yield 1, 5 + (yield 3));
console.log(res);
console.log('my qq: ' + (yield qq)); // yield 在一个语句中需要括起来
}
fn();
Generator异常捕获机制
在Generator函数执行过程中,可能会遇到异常。正确处理这些异常对于保证程序的健壮性至关重要。
第一种异常捕获方式是通过throw方法手动抛出错误。当调用Generator实例的throw()方法时,会立即抛出指定的错误,导致当前执行中断并向上层抛出异常,除非在Generator内部有对应的try...catch块捕获该错误。
function * fn() {
let qq = yield; // yield 默认返回undefined, 不会抛出异常
console.log(qq);
}
let g = fn();
g.next(); // 第一个断点没有输出
// g.next('qq 11111'); // 完毕之后传值输出:qq 11111
g.throw('error!'); // Uncaught error!
第二种异常捕获方式是在Generator函数内部使用try...catch块。当外部通过throw()方法抛出错误时,内部catch块可以捕获该错误并进行处理,从而避免程序崩溃。同时,finally块中的代码无论是否发生异常都会执行。
function * fn() {
let qq;
try {
qq = yield; // yield 默认返回undefined
}catch(e){
console.log('qq have error');
}finally{
console.log(qq);
}
}
let g = fn();
g.next();
g.throw('error!');
// qq have error
// undefined
第三种异常捕获方式是通过try...catch包裹Generator的调用过程。如果在Generator外部调用throw()方法导致错误抛出,可以在外部使用try...catch捕获该错误,从而对异常进行统一处理。
function * fn() {
let qq;
qq = yield;
console.log(qq);
}
let g = fn();
g.next();
try{
g.throw('error!');
}catch(e){
console.log('qq have error!');
}
第四种情况涉及非yield语句引发的异常。如果yield表达式右侧存在未定义的变量或引用错误,即使有try...catch包裹,也可能导致赋值失败或逻辑异常。此时,错误可能不会按预期被捕获,需特别注意代码逻辑的严密性。
function * fn() {
let qq;
try {
qq = yield ff; // ff 未定义, 所以qq不会被正确赋值 此处是非 yield 的异常
}catch(e){
console.log('err1');
}
console.log(qq);
}
let g = fn();
g.next();
g.next('qq 5554');
// err1
// undefined
Generator与Promise结合应用
结合Generator与Promise可以实现更优雅的异步编程模式。通过自定义执行器,可以将异步逻辑以同步代码的形式书写,从而避免回调地狱,提高代码的可读性和维护性。
function asyncF(name) {
return new Promise(function(resolve){
setTimeout(function(){
resolve('my name is ' + name);
});
});
}
function * fn() {
console.log(yield asyncF('Joh'));
}
let gf = fn();
function exec(gf,value) {
let res = gf.next(value);
if(!res.done) {
if(res.value instanceof Promise) {
res.value.then(function (v) {
exec(gf, v);
})
}else{
exec(gf, res.value);
}
}
}
exec(gf); // my name is Joh
更复杂的异步流程可以通过封装执行器来实现。该执行器负责自动推进Generator的执行,处理Promise的resolve/reject状态,并将结果传递回Generator内部,实现异步逻辑的线性化表达。
function asyncF(name) {
return new Promise(function(resolve){
setTimeout(function(){
resolve('my name is ' + name);
});
});
}
function sum(a, b) {
return new Promise(function (resolve) {
setTimeout(function () {
resolve(a + b);
});
})
}
function * fn(name) {
if((yield sum(3,5)) > 6) {
console.log(yield asyncF(name));
}else{
console.log('error');
}
}
let gf = fn('Joh');
// generator 执行器 相当于 tj/co 模块
function exec(gf,value) {
let res = gf.next(value);
if(!res.done) {
if(res.value instanceof Promise) {
res.value.then(function (v) {
exec(gf, v);
})
}else{
exec(gf, res.value);
}
}
}
exec(gf); // my name is Joh
使用纯Promise实现异步流程控制时,通常需要手动处理Promise链的回调,代码较为繁琐。相比之下,结合Generator的方式可以显著简化异步代码的结构,使逻辑更加清晰。
function asyncF(name) {
return new Promise(function(resolve){
setTimeout(function(){
resolve('my name is ' + name);
});
});
}
function sum(a, b) {
return new Promise(function (resolve) {
setTimeout(function () {
resolve(a + b);
});
})
}
function fn(name) {
sum(3,5)
.then(function (num) {
if(num > 6) {
asyncF(name)
.then(function (v) {
console.log(v);
})
}else{
console.log('error');
}
})
}
fn('Joh');
使用co模块简化异步流程
为了简化Generator与Promise的结合使用,可以使用co模块。co模块提供了自动执行Generator函数的功能,无需手动编写执行器,从而极大地简化了异步编程的复杂度,是处理异步逻辑的强大工具。
var co = require('co');
function asyncF(name) {
return new Promise(function(resolve){
setTimeout(function(){
resolve('my name is ' + name);
});
});
}
function sum(a, b) {
return new Promise(function (resolve) {
setTimeout(function () {
resolve(a + b);
});
})
}
function * fn(name) {
if((yield sum(3,5)) > 6) {
console.log(yield asyncF(name));
}else{
console.log('error');
}
}
var fnx = co.wrap(fn);
fnx('Joh'); // my name is Joh
更多关于JavaScript相关内容可查看本站专题:《javascript面向对象入门教程》、《JavaScript切换特效与技巧总结》、《JavaScript查找算法技巧总结》、《JavaScript错误与调试技巧总结》、《JavaScript数据结构与算法技巧总结》、《JavaScript遍历算法与技巧总结》及《JavaScript数学运算用法总结》
希望本文关于ES6 Generator函数的详细解析与实例演示,能对读者的JavaScript程序设计有所帮助。
ES6模块化中js文件怎么用import和export交互
2026-11-05 17:00:48
JS读取本地文件时报错或无响应的常见原因
2026-11-05 16:32:31
let、const与var的区别:JavaScript变量声明最佳实践
2026-11-05 14:16:38
JS中直接定义静态二维数组的字面量写法
2026-11-05 14:16:08
JS 怎么查找字符串中所有匹配项的位置
2026-11-05 14:15:53
