Generator
函数是ES6提供的一种异步编程解决方案,语法行为与传统函数完全不同
前面的文章里我们介绍了回调函数和 promise 这两种手段来解决异步,本文将继续介绍异步发展史上的另外两种方法:Generator和async/await
Generater
执行 Generator 函数会返回一个遍历器对象,也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。
形式上,Generator 函数是一个普通函数,但是有两个特征。一是,function关键字与函数名之间有一个星号;二是,函数体内部使用yield表达式,定义不同的内部状态(yield在英语里的意思就是“产出”)
关键特性:
- 声明方式: Generator函数使用
function*
语法声明,星号(*
)紧跟着function
关键字后面。 - Yield表达式:
yield
表达式用于暂停和恢复函数的执行。每次函数在yield
处暂停时,它会返回yield
后面的值给调用者。当再次调用生成器的next()
方法时,函数会从上一个yield
表达式之后的位置继续执行,直到下一个yield
或函数结束。 - 返回遍历器对象: 执行Generator函数时,它不立即执行函数体内的代码,而是返回一个遍历器对象(iterator object)。这个对象具有
next()
方法,每次调用next()
方法都会执行函数直到下一个yield
表达式,或者直到函数结束。 - 遍历器协议: 遍历器对象遵循遍历器协议,
next()
方法返回一个对象,该对象有value
和done
两个属性。value
是yield
表达式的结果,done
是一个布尔值,表示是否到达了生成器函数的末尾。
函数定义:
Generator函数的定义与普通函数类似,但是有以下几点不同:
- 函数声明前面需要加上星号
*
,表明这是一个Generator函数。 - 函数体内可以使用
yield
表达式来“产出”一系列的值。yield
表达式的作用类似于return
,但不会结束函数,而是暂时挂起函数的执行,并在下次调用时从暂停的地方继续执行。
使用方法:
- 创建Generator对象:调用
Generator
函数时,它不会立即执行函数体内的代码,而是返回一个Generator
对象。这个对象实现了迭代器协议,拥有next()
方法。
因为此对象返回Iterator
对象,所以我们可以通过for...of
进行遍历
function* hai(){
yield '嗨!';
yield '你好吗?';
yield '我很好!';
}
for (let h of hai()) {
console.log(h);
}
-
next()方法:调用Generator对象的
next()
方法会执行Generator函数直到遇到下一个yield
表达式,或者直到函数结束。next()
方法返回一个对象,该对象有两个属性:value
:yield
表达式产生的值,如果没有更多的yield
表达式,则为undefined
(yield
本身没有返回值,所以返回undefined
)。done
:一个布尔值,表示是否到达了Generator函数的末尾。
运行逻辑:
- 遇到
yield
表达式,就暂停执行后面的操作,并紧紧跟在yield
后面的那个表达式的值,作为返回对象的value
属性值。 - 下次调用
next
方法时,再继续往下执行,直到遇到下一个yield
表达式 - 如果没有再遇到新的
yield
方法,就一直运行到函数结束,直到return
语句为止,并将return
语句后面的表达式的值,作为返回对象的value
值 - 如果该函数没有
return
语句,则返回的对象value
属性值为undefind
function* hai(){
yield '嗨!';
yield '你好吗?';
yield '我很好!';
}
var h = hai();
h.next()
h.next()
h.next()
h.next()
- 发送值给Generator函数:
next()
方法还可以接受一个参数,这个参数将作为上一个yield
表达式的值,从而可以向Generator函数内部发送数据。
function* foo(x) {
var y = 2 * (yield (x + 1));
var z = yield (y / 3);
return (x + y + z);
}
var a = foo(5);
a.next()//{value: 6, done: false}
a.next()//{value: NaN, done: false}
a.next()//value: NaN, done: true}
var b = foo(5);
b.next()//{value: 6, done: false}
b.next(12)//{value: 8, done: false}
b.next(13)//{value: 42, done: true}
a.next()
- 第一次
foo
函数开始执行,遇到第一个yield
表达式(x + 1)
,计算x + 1
的值,即5 + 1 = 6
。6
被作为a.next()
的value
属性返回,done
属性为false
表示生成器还没有执行完毕。
- 第二次
foo
函数继续执行,遇到var y = 2 * (yield (x + 1));
,由于上一个yield
表达式的值被忽略了(没有通过a.next(value)
提供),因此y
的值是2 * NaN = NaN
。- 接下来遇到
var z = yield (y / 3);
,返回y / 3
的值,即NaN / 3 = NaN
。 NaN
被作为a.next()
的value
属性返回,done
属性为false
表示生成器还没有执行完毕
- 第三次
foo
函数继续执行,遇到return (x + y + z);
,计算x + y + z
的值,即5 + NaN + NaN = NaN
。NaN
被作为a.next()
的value
属性返回,done
属性为true
表示生成器已经执行完毕。
b.next()
- 第一次
foo
执行,遇到第一个yield
表达式(x + 1)
,计算x + 1
的值,即5 + 1 = 6
。6
被作为b.next()
的value
属性返回,done
属性为false
表示生成器还没有执行完毕。
- 第二次
- 遇到
var y = 2 * (yield (x + 1));
,由于上一个yield
表达式的值被设置为12
,因此y
的值是2 * 12 = 24
。 - 接下来遇到
var z = y / 3;
,计算y / 3
的值,即24 / 3 = 8
。 8
被作为b.next()
的value
属性返回,done
属性为false
表示生成器还没有执行完毕。
- 第三次
- 这一行代码执行后,
foo
函数继续执行,遇到yield (z);
,由于上一个yield
表达式的值被设置为13
,但这里13
不会被使用。 - 接下来遇到
return (x + y + z);
,计算x + y + z
的值,即5 + 24 + 8 = 37
。 37
被作为b.next()
的value
属性返回,done
属性为true
表示生成器已经执行完毕。
示例
function* countUpTo(max) {
for (let i = 1; i <= max; i++) {
yield i;
}
}
const counter = countUpTo(3);
// 第一次调用next(),从1开始
console.log(counter.next()); // { value: 1, done: false }
// 第二次调用next(),输出2
console.log(counter.next()); // { value: 2, done: false }
// 最后一次调用next(),输出3
console.log(counter.next()); // { value: 3, done: false }
// 再次调用next(),Generator函数已经结束
console.log(counter.next()); // { value: undefined, done: true }
async/await
async/await
是基于Promise的一种更高级的异步编程语法糖。async
函数总是返回一个Promise。await
关键字只能在async
函数内部使用,用于等待Promise解析。- 使用
await
关键字可以让异步代码看起来和同步代码非常相似,极大地提高了可读性和可维护性。 async/await
是目前处理异步操作最推荐的方式,因为它简洁且易于理解。
使用方法
async/await
形式简洁,直接在函数前加上async然后在需要异步的操作前加上await即可
下面是使用 async/await
的基本步骤和示例:
步骤
-
定义异步函数:
- 使用
async
关键字定义函数。 - 这个函数自动返回一个
Promise
。
- 使用
-
使用
await
关键字:- 在
async
函数内部,你可以使用await
关键字等待一个Promise
完成。 await
只能在async
函数内部使用。- 当
await
一个Promise
时,函数会暂停执行,直到Promise
解析(resolve
)或拒绝(reject
)。 - 如果
Promise
成功解析,await
表达式的值就是resolve
的值;如果Promise
被拒绝,则会抛出一个错误。
- 在
示例
假设我们有一个异步函数 getData
用于从服务器获取数据,然后我们使用 async/await
来处理这个异步操作。
Step 1: 定义数据获取函数
function getData() {
return new Promise((resolve) => {
setTimeout(() => {
resolve('Hello from server!');
}, 2000); // 模拟网络延迟
});
}
Step 2: 使用 async/await
获取数据
async function a() {
console.log(1);
const data = await getData(); // 等待数据获取完成
console.log(2);
console.log(data);
}
// 调用异步函数
a();
解释
-
定义异步函数:
- 使用
async
关键字定义了一个函数a
。 - 这个函数自动返回一个
Promise
。
- 使用
-
使用
await
关键字:- 在
async
函数内部,我们使用await
关键字等待getData()
函数返回的Promise
完成。 await
只能在async
函数内部使用。- 当
await
一个Promise
时,函数会暂停执行,直到Promise
解析(resolve
)或拒绝(reject
)。 - 如果
Promise
成功解析,await
表达式的值就是resolve
的值;如果Promise
被拒绝,则会抛出一个错误。
- 在
-
调用异步函数:
- 最后,我们可以通过简单地调用
a()
来启动整个流程。 - 由于
a
返回一个Promise
,可以通过.then()
或.catch()
方法来处理它的结果或错误。
- 最后,我们可以通过简单地调用
运行示例
当运行这个示例时,会看到以下输出:
Hello from server!
这是因为:
- 函数
a()
首先打印数字1
。 - 然后等待
getData()
返回的数据,该数据将在 2 秒后可用。 - 当数据准备好时,
a()
函数继续执行,打印数字2
和获取到的数据'Hello from server!'
。
异步解决方案的区别
- promise和async/await是专门用于处理异步操作的
- Generator 并不是为异步而设计出来的,它还有其他功能(对象迭代、控制输出、部署Interator接口等)
- promise编码相比Generator、async更为复杂化,且可读性也稍差
- Generator、async需要与promise对象搭配处理异步情况
- async实质上是Generator的语法糖,相当于会自动执行的Generator函数
- async使用上更为简洁,将异步代码以同步形式进行编写,是处理异步编程的最终方案
以上就是本文全部内容,希望对你有所帮助,感谢你的阅读!