Async Generator Functions in Jav

作者: 范小饭_ | 来源:发表于2019-08-15 09:23 被阅读3次

    原文《Async Generator Functions in JavaScript
    翻译:范小饭

    将for/await/of引入javascript的TC39异步迭代器提议还引入了异步生成器函数的概念。现在,javascript有6种不同的函数类型。

    • 普通函数 function() {}
    • 箭头函数 () => {}
    • 异步函数 async function() {}
    • 异步箭头函数 async () => {}
    • 生成器函数 function*() {}
    • 异步生成器函数 async function*() {}

    异步生成器函数是特别的,因为你可以在函数中同时使用await 和 yield, 异步生成器函数不同于异步函数和生成器函数,因为它不会返回promise或者iterator,而是返回异步迭代器,你可以把异步迭代器作为一个next()函数总是返回一个promise的迭代器。

    你的第一个异步迭代器

    异步迭代器函数行为与迭代器函数相似,迭代器函数返回一个有next函数的对象,调用next()将执行generator函数,直到下一个yield。不同的是,异步迭代器的next() 返回一个promise

    下面是一个异步生成器函数的"Hello, World"的例子。请注意,下面的脚本在10.x之前的node.js版本上不起作用。

    'use strict';
    
    async function* run() {
      await new Promise(resolve => setTimeout(resolve, 100));
      yield 'Hello';
      console.log('World');
    }
    
    // `run()` returns an async iterator.
    const asyncIterator = run();
    
    // The function doesn't start running until you call `next()`
    asyncIterator.next().
      then(obj => console.log(obj.value)). // Prints "Hello"
      then(() => asyncIterator.next());  // Prints "World"
    

    最干净的方法是使用for/await/of循环整个异步生成器函数。

    'use strict';
    
    async function* run() {
      await new Promise(resolve => setTimeout(resolve, 100));
      yield 'Hello';
      console.log('World');
    }
    
    const asyncIterator = run();
    
    // Prints "Hello\nWorld"
    (async () => {
      for await (const val of asyncIterator) {
        console.log(val); // Prints "Hello"
      }
    })();
    
    一个实际的用例

    你可能在想,为什么有了异步函数和生成器函数,JavaScript还需要异步生成器函数?一个用例是RyanDahl最初编写node.js来解决的经典进度条问题。

    假设您希望遍历Mongoose光标中的所有文档,并通过WebSocket或命令行报告进度。

    'use strict';
    
    const mongoose = require('mongoose');
    
    async function* run() {
      await mongoose.connect('mongodb://localhost:27017/test', { useNewUrlParser: true });
      await mongoose.connection.dropDatabase();
    
      const Model = mongoose.model('Test', mongoose.Schema({ name: String }));
      for (let i = 0; i < 5; ++i) {
        await Model.create({ name: `doc ${i}` });
      }
    
      // Suppose you have a lot of documents and you want to report when you process
      // each one. You can `yield` after processing each individual doc.
      const total = 5;
      const cursor = Model.find().cursor();
    
      let processed = 0;
      for await (const doc of cursor) {
        // You can think of `yield` as reporting "I'm done with one unit of work"
        yield { processed: ++processed, total };
      }
    }
    
    (async () => {
      for await (const val of run()) {
        // Prints "1 / 5", "2 / 5", "3 / 5", etc.
        console.log(`${val.processed} / ${val.total}`);
      }
    })();
    

    异步生成器函数使您的异步函数能够以无框架的方式报告其进度,不需要明确的创建websocket或者console.log,假设业务逻辑使用yield进行进度报告,你可以使用yield单独处理。

    可以观测的

    异步迭代器很棒,但还有另一个并发原语,即异步生成器函数与RxJS observables也能很好的对应上。

    'use strict';
    
    const { Observable } = require('rxjs');
    const mongoose = require('mongoose');
    
    async function* run() {
      // Same as before
    }
    
    // Create an observable that emits each value the async generator yields
    // to subscribers.
    const observable = Observable.create(async (observer) => {
      for await (const val of run()) {
        observer.next(val);
      }
    });
    
    // Prints "1 / 5", "2 / 5", "3 / 5", etc.
    observable.subscribe(val => console.log(`${val.processed} / ${val.total}`));
    

    RxJS observable和异步迭代器有2个关键区别,
    1、在上面的示例中,在subscribe()中记录到控制台的代码是反应式的,而不是命令式的。换句话说,subscribe()处理程序无法影响异步函数体中的代码,它只对事件作出反应,当用一个for/await/of 循环,例如,当使用for/await/of循环时,可以在恢复异步生成器函数之前添加一秒钟的暂停

    (async () => {
      for await (const val of run()) {
        // Prints "1 / 5", "2 / 5", "3 / 5", etc.
        console.log(`${val.processed} / ${val.total}`);
        // This adds a 1 second delay to every `yield` statement.
        await new Promise(resolve => setTimeout(resolve, 1000));
      }
    })();
    

    第二个就是,由于RXJS observable在默认情况下是冷的,所以一个新的subscribe()调用会重新执行该函数。

    // Prints "1 / 5", "2 / 5", "3 / 5", etc.
    observable.subscribe(val => console.log(`${val.processed} / ${val.total}`));
    // Kicks off a separate instance of `run()`
    observable.subscribe(val => console.log(`${val.processed} / ${val.total}`));
    

    Moving On

    异步生成器函数一开始可能看起来令人困惑,但它们提供了可能成为javascript的进度条问题的本机解决方案,使用yield报告异步函数的进度是一个诱人的想法,因为它允许您将业务逻辑与进度报告框架分离。下次需要实现进度条时,给异步生成器一个机会。

    相关文章

      网友评论

        本文标题:Async Generator Functions in Jav

        本文链接:https://www.haomeiwen.com/subject/fphnjctx.html