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 function2,更容易编写和阅读。下面是那个例子的生成器实现,可以看到简单了亿点点:

/**
 * @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/await4,我们只需要偷走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}…;剩下的东西就不用我在啰嗦了吧。



  1. 迭代协议 - JavaScript | MDN (mozilla.org) ↩︎

  2. 迭代器和生成器 - JavaScript | MDN (mozilla.org) ↩︎

  3. async function* - JavaScript | MDN (mozilla.org) ↩︎

  4. await - JavaScript | MDN (mozilla.org) ↩︎