Skip to content

异步 Generator 函数 ​

本节目标:理解“异步 Generator 函数”的核心思路,并能把它用于实际开发或面试表达。 就像 Generator 函数返回一个同步遍历器对象一样,异步 Generator 函数的作用,是返回一个异步遍历器对象。 在语法上,异步 Generator 函数就是async函数与 Generator 函数的结合。

async function*gen(){yield 'hello';}const genObj =gen();genObj.next().then(x =>console.log(x));// { value: 'hello', done: false }

上面代码中,gen是一个异步 Generator 函数,执行后返回一个异步 Iterator 对象。对该对象调用next方法,返回一个 Promise 对象。 异步遍历器的设计目的之一,就是 Generator 函数处理同步操作和异步操作时,能够使用同一套接口。 // 同步 Generator 函数

function*map(iterable,func){const iter =iterable[Symbol.iterator]();while(true){const {value,done}=iter.next();if(done)break;yield func(value);}}//

异步 Generator 函数

async function*map(iterable,func){const iter =iterable[Symbol.asyncIterator]();while(true){const {value,done}=await iter.next();if(done)break;yield func(value);}}

上面代码中,map是一个 Generator 函数,第一个参数是可遍历对象iterable,第二个参数是一个回调函数func。map的作用是将iterable每一步返回的值,使用func进行处理。上面有两个版本的map,前一个处理同步遍历器,后一个处理异步遍历器,可以看到两个版本的写法基本上是一致的。 下面是另一个异步 Generator 函数的例子。

async function*readLines(path){letfile =await fileOpen(path);try{while(!file.EOF){yield await file.readLine();}}finally{await file.close();}}

上面代码中,异步操作前面使用await关键字标明,即await后面的操作,应该返回 Promise 对象。凡是使用yield关键字的地方,就是next方法停下来的地方,它后面的表达式的值(即await file.readLine()的值),会作为next()返回对象的value属性,这一点是与同步 Generator 函数一致的。 异步 Generator 函数内部,能够同时使用await和yield命令。可以这样理解,await命令用于将外部操作产生的值输入函数内部,yield命令用于将函数内部的值输出。 上面代码定义的异步 Generator 函数的用法如下。

(async function(){forawait (const line of readLines(filePath)){console.log(line);}})()

异步 Generator 函数可以与for await...of循环结合起来使用。

async function*prefixLines(asyncIterable){forawait (const line of asyncIterable){yield '> '+line;}}

异步 Generator 函数的返回值是一个异步 Iterator,即每次调用它的next方法,会返回一个 Promise 对象,也就是说,跟在yield命令后面的,应该是一个 Promise 对象。如果像上面那个例子那样,yield命令后面是一个字符串,会被自动包装成一个 Promise 对象。

functionfetchRandom(){const url ='https://www.random.org/decimal-fractions/'+'?num=1&dec=10&col=1&format=plain&rnd=new';returnfetch(url);}async function*asyncGenerator(){console.log('Start');const result =await fetchRandom(); // (A)yield 'Result: '+await result.text(); // (B)console.log('Done');}const ag =asyncGenerator();ag.next().then(({value,done})=>{console.log(value);})

上面代码中,ag是asyncGenerator函数返回的异步遍历器对象。调用ag.next()以后,上面代码的执行顺序如下。

  1. ag.next()立刻返回一个 Promise 对象。
  2. asyncGenerator函数开始执行,打印出Start。
  3. await命令返回一个 Promise 对象,asyncGenerator函数停在这里。
  4. A 处变成 fulfilled 状态,产生的值放入result变量,asyncGenerator函数继续往下执行。
  5. 函数在 B 处的yield暂停执行,一旦yield命令取到值,ag.next()返回的那个 Promise 对象变成 fulfilled 状态。
  6. ag.next()后面的then方法指定的回调函数开始执行。该回调函数的参数是一个对象{value, done},其中value的值是yield命令后面的那个表达式的值,done的值是false。

A 和 B 两行的作用类似于下面的代码。

returnnewPromise((resolve,reject)=>{fetchRandom().then(result =>result.text()).then(result =>{resolve({value:'Result: '+result,done:false,});});});

如果异步 Generator 函数抛出错误,会导致 Promise 对象的状态变为reject,然后抛出的错误被catch方法捕获。

async function*asyncGenerator(){thrownewError('Problem!');}asyncGenerator().next().catch(err =>console.log(err)); // Error: Problem!

注意,普通的 async 函数返回的是一个 Promise 对象,而异步 Generator 函数返回的是一个异步 Iterator 对象。可以这样理解,async 函数和异步 Generator 函数,是封装异步操作的两种方法,都用来达到同一种目的。区别在于,前者自带执行器,后者通过for await...of执行,或者自己编写执行器。下面就是一个异步 Generator 函数的执行器。

async functiontakeAsync(asyncIterable,count =Infinity){const result =[];const iterator =asyncIterable[Symbol.asyncIterator]();while(result.length <count){const {value,done}=await iterator.next();if(done)break;result.push(value);}returnresult;}

上面代码中,异步 Generator 函数产生的异步遍历器,会通过while循环自动执行,每当await iterator.next()完成,就会进入下一轮循环。一旦done属性变为true,就会跳出循环,异步遍历器执行结束。 下面是这个自动执行器的一个使用实例。

async functionf(){async function*gen(){yield 'a';yield 'b';yield 'c';}returnawait takeAsync(gen());}f().then(function(result){console.log(result); // ['a', 'b', 'c']})

异步 Generator 函数出现以后,JavaScript 就有了四种函数形式:普通函数、async 函数、Generator 函数和异步 Generator 函数。请注意区分每种函数的不同之处。基本上,如果是一系列按照顺序执行的异步操作(比如读取文件,然后写入新内容,再存入硬盘),可以使用 async 函数;如果是一系列产生相同数据结构的异步操作(比如一行一行读取文件),可以使用异步 Generator 函数。 异步 Generator 函数也可以通过next方法的参数,接收外部传入的数据。

const writer =openFile('someFile.txt');writer.next('hello'); //

立即执行

writer.next('world'); //

立即执行

await writer.return(); //

等待写入结束 上面代码中,openFile是一个异步 Generator 函数。next方法的参数,向该函数内部的操作传入数据。每次next方法都是同步执行的,最后的await命令用于等待整个写入操作结束。 最后,同步的数据结构,也可以使用异步 Generator 函数。

async function*createAsyncIterable(syncIterable){for(const elem of syncIterable){yield elem;}}

上面代码中,由于没有异步操作,所以也就没有使用await关键字。 yield* 语句yield*语句也可以跟一个异步遍历器。

async function*gen1(){yield 'a';yield 'b';return2;}async function*gen2(){ // result

最终会等于

 2const result =yield*gen1();}

上面代码中,gen2函数里面的result变量,最后的值是2。 与同步 Generator 函数一样,for await...of循环会展开yield*。

(async function(){forawait (const x of gen2()){console.log(x);}})();// a// b

> 来自

 <https://es6.ruanyifeng.com/#docs/async-iterator>

本站总访问 — 次