Javascript迭代器、生成器、异步
1 Javascript 迭代器
ES6 新增了一个新玩具iterator:迭代器iterator
协议1主要规定了可迭代协议允许 JavaScript 对象定义或定制它们的迭代行为,例如,在一个 for..of
结构中,哪些值可以被遍历到。一些内置类型同时是内置的可迭代对象,并且有默认的迭代行为,比如 Array
或者 Map
,而其他内置类型则不是(比如 Object
)。下面是一个简单的fibonacci迭代器,可以无限生产一个数列1,1,2,3,5,8...
.
/**
* @description function *迭代器对象
* @returns a fibonacci iterable object
*/
const fib = {
[Symbol.iterator]() {
let n1 = 1, n2 = 1
return {
// 使迭代器编程iterable
[Symbol.iterator]() { return this; },
next() {
let c = n2;
n2 = n1
n1 = n1 + c
return { value: c, done: false };
},
return(v) {
console.log("Fibonacci seq abandoned")
return { value: v, done: true };
}
}
}
}
for (let v of fib) {
console.log(v)
if (v > 50) break;
}
2. 生成器
由于迭代器的语法太过繁琐,ES6为我们提供一个很甜的语法糖generator function
2,更容易编写和阅读。下面是那个例子的生成器实现,可以看到简单了亿点点:
/**
* @description function *生成器函数 (generator function)
* @returns a fibonacci generator
*/
// fibonacci generator
function* fib_generator() {
let n1 = 1, n2 = 1
while (true) {
let c = n2;
n2 = n1
n1 = n1 + c
yield c // return {value=?, done=?}
}
}
const gen = fib_generator();
console.log(gen.next().value);// expected output: 1
console.log(gen.next().value);// expected output: 1
for (let i = 0; i < 10; ++i) {
console.log(gen.next().value);
}
for (let v of fib_generator()) {
console.log(v)
if (v > 50) break;
}
// take n elements from a generator
function* take(g, n) {
let _g = g()
let i = 0;
while (i++ < n) {
let next = _g.next()
if (next.done) break;
yield next.value
}
}
// test take~
for (let x of take(fib_generator, 5)) {
console.log(x)
}
// take elements from a generator , with elements `x` fullfill f(x)=true
function* take_while(g, f) {
let _g = g()
while (true) {
let next = _g.next()
if (next.done || !f(next.value)) break;
yield next.value
}
}
for (let x of take_while(fib_generator, (x)=>x<100)){
console.log(x)
}
好吧,我承认是加了一些私货。下面的take/take_while
是一个高阶函数,用于从生成器提取若干、满足条件的结果。注意他们本身也是生成器:)
2 异步生成器3
好了,现在开始上难度!来一个异步生成器锻炼下大脑。
f = (i) => new Promise(resolve => {
setTimeout(() => {
resolve(i)
}, Math.random() * 1000)
})
// 异步生成器~ [async function*]
async function* foo() {
for (let i = 10; i >= 0; --i) {
yield await f(i)
}
}
async function generate() {
for await (const val of foo()) { // 注意 for 后面的await~
console.log(val);
}
//console.log(str);
}
generate(); // Expected output: "10 9 8 7...",模拟随机时间间隔输出。
这里的foo没有啥特别的,就是生成器加上一个async关键字,变为返回异步Promise。主要是generate里面,for必须await,如不加这个等待,foo直接返回,无法获取到值。官网给出了一个实用的例子用于读取文件目录:
async function* readFiles(directory) {
const files = await fs.readdir(directory);
for (const file of files) {
const stats = await fs.stat(file);
if (stats.isFile()) {
yield {
name: file,
content: await fs.readFile(file, "utf8"),
};
}
}
}
const files = readFiles(".");
console.log((await files.next()).value);
// 可能的输出;{ name: 'file1.txt', content: '...' }
console.log((await files.next()).value);
// 可能的输出:{ name: 'file2.txt', content: '...' }
3 来实现一个生产者-消费者设计!
首先我们需要设计一个锁来帮助同步,由Promise/await
4,我们只需要偷走Promise里面的resolve(lock),放在合适的地方调用(unlock),即可完成一个简单的锁,不多说,看注释:
class lock {
constructor() {
this.locked = null;
}
isLocked() { return this.locked !== null }
lock = async function () {
if (this.locked === null) {
this.locked = [];
// console.log('locked')
}
else
await new Promise(resolve => {
// console.log('blocked', resolve)
return this.locked.push(resolve) // SAVE resolve【1】
});
}
unlock = function () {
if (this.locked && this.locked.length) {
//this.locked.shift()();
const resolve = this.locked.splice(Math.random() * this.locked.length | 0, 1)[0]
// console.log('unlocked', resolve)
resolve() // 解锁后pop的Promise 【1】处返回,继续执行。
}
else
this.locked = null;
};
}
// 休眠mili-sec.
const sleep = async (msec) => new Promise(resolve =>
setTimeout(() => resolve(), msec)
)
// test with 'await f()'
async function f() {
await sleep(1500); // 1.5秒后执行后面的语句
console.log("Yoo!")
}
module.exports = { lock, sleep }
利用这个锁,我们很容易设计一个生产者-消费者模型,如下:
const {
lock, sleep // Define a variable t.
} = require('./lock')
const max_ = 10 // 队列最大值
const lck_empty = new lock(); // lock if queue empty
const lck_full = new lock();// lock if queue full[>max]
let msg_id = 0;// 消息ID
//生产者-消费者设计模式
async function Producer(id, queue) {
while (true) {
if (queue.length > max_) {
console.log(`Producer${id} 等待消费`)
await lck_full.lock(); // 等待消费
}
console.log(`Producer ${id}`)
await sleep(1000)
queue.push({ id, msg: msg_id++ })
console.log('Producer#', id, "->", msg_id)
lck_empty.unlock(); // 通知消费
}
}
async function Consumer(id, queue, resolve) {
while (true) {
// acuire message from queue.~
if (queue.length == 0) {
console.log(`Consumer${id} 等待生产`)
await lck_empty.lock(); // 等待生产
}
await sleep(2000)
resolve(queue.shift())
lck_full.unlock(); // 通知生产
}
}
class MessageQueue {
constructor() {
this.queue = []
}
async createProcuder() {
for (let i = 0; i < 3; ++i) {
console.log('create p', i)
Producer(i, this.queue)
}
}
async createConsumer() {
for (let i = 0; i < 2; ++i) {
console.log('create c', i)
Consumer(i, this.queue, ({ id, msg }) => { console.log(`Consumer${id}`,"<=p#", id, "msg:", msg) })
}
}
}
console.clear()
mq = new MessageQueue()
mq.createConsumer();
mq.createProcuder()
可能async/await模型对于新手还是有点难,我总结的规律是,async类似spawn,await就是一个同步标记。所以可以看到createProcuder()
里面调用Producer
并不需要await,因为我需要生产N个并发的生产者;而Producer
函数内部需要保证顺序执行,必须在sleep,lock
等函数调用时加await!
那么问题来了,如何将上述生产者改成异步生成器?提示:只需修改下queue.push({ id, msg: msg_id++ })
为yield {id,msg}
…;剩下的东西就不用我在啰嗦了吧。