ES9 异步 Generator

就像 Generator 函数返回一个同步遍历器对象一样,异步 Generator 函数的作用,是返回一个异步遍历器对象。

简介

就像 Generator 函数返回一个同步遍历器对象一样,异步 Generator 函数,返回一个异步遍历器。

一个简单的例子

async function* gen() {
    yield 'hello';
    yield 'world';
}

const genObj = gen();

genObj.next().then(x => console.log(x));
// { value: 'hello', done: false }

genObj.next().then(x => console.log(x));
// { value: 'world', done: false }

异步 Generator 函数在写法上与同步 Generator 函数仅仅相差了 async 关键字,最主要的区别是在 next 方法的调用上,同步 Generator 函数调用 next 方法返回一个 { value: any, done: boolean } 的对象,而异步 Generator 函数返回一个 Promise 对象,其异步操作的结果为 { value: any, done: boolean }

为了方便称呼,异步 Generator 函数称为 async Generator,同步 Generator 函数为 Generator

之前在 Generator 函数中提到过,该函数的作用之一为:部署在 Iterator 接口上,实现数据的遍历。async Generator 函数同样的也可以部署在一个专门用于异步遍历的接口 asyncIterator ,那上面是 asyncIterator 接口?。

asyncIterator 接口

异步遍历器的最大的语法特点,就是调用遍历器的 next 方法,返回的是一个 Promise 对象。

一个简单的符合 asyncIterator 接口函数:

function createAsyncIterable(arr){
    let arrCopy = [...arr];
    return {
        next(){
            return Promise.resolve({
                value: arrCopy.pop(),
                done: arrCopy.length === 0
            });
        }
    }
}

let ai = createAsyncIterable(['hello', 'world'])

ai.next().then(x => console.log(x));
// { value: 'hello', done: false }

ai.next().then(x => console.log(x));
// { value: 'world', done: true }

形式上与同步 Iterator 保持较高程度的一致性,不同点仅有一点:next 方法不直接返回数据,而是返回 Promise ,所以数据的获取应该在 then 方法中,并且 Promise 中返回的数据应该与同步的一致: { value: any, done: true }

异步遍历器的简单使用:

let ai = createAsyncIterable(['hello', 'world'])

// 直接使用
ai.next();
.then(iterResult1 => {
    console.log(iterResult1);
    // { value: 'hello', done: false }
    // 获取下一次遍历的 Promise;
    return ai.next();
})
.then(iterResult2 => {
    console.log(iterResult2);
    // { value: 'world', done: true }
})

// 使用 async/await
async function f() {
    let ai = createAsyncIterable(['hello', 'world']);
    console.log(await ai.next());
    // { value: 'hello', done: false }
    console.log(await ai.next());
    // { value: 'hello', done: true }
}

方式一使用 Promise 一步步调用,但是这种写法带着一股浓浓的异步风采。

第二种使用 async/await 很明显这已经和同步写法差不多了,所以较为推荐使用第二种写法,去遍历异步 Iterator 接口。

for await...of

之前说到过,为了配合 Iterator 接口,ES6 规定了一种新的遍历方式 for...of 循环,同样的为了配合 asyncIterator 接口,规定了 for await...of 循环。

一个例子:

// asyncIterator 接口部署在对象的 Symbol.asyncIterator 属性上
let obj = {
    arr: ['world', 'hello'],
    [Symbol.asyncIterator](){
        let arrCopy = [...this.arr]
        return {
            next(){
                return Promise.resolve({
                    done: arrCopy.length === 0,
                    value: arrCopy.pop()
                });
            }
        }
    }
}

async function f() {
  for await (const x of obj) {
    console.log(x);
  }
}

f();
// hello
// world

如果还记得同步遍历器的编写方式的话,相信看到这些代码会很眼熟,区别仅仅只有两点

  1. 接口名称为:Symbol.asyncIterator
  2. 使用 for await...of 循环,还有函数前的 async

可能这么说不会觉的异步遍历器的强大,一个简单的例子:

Node v10 支持异步遍历器,Stream 就部署了这个接口。下面是读取文件的传统写法与异步遍历器写法的差异。

// 传统写法
function main(inputFilePath) {
  const readStream = fs.createReadStream(
    inputFilePath,
    { encoding: 'utf8', highWaterMark: 1024 }
  );
  readStream.on('data', (chunk) => {
    console.log('>>> '+chunk);
  });
  readStream.on('end', () => {
    console.log('### DONE ###');
  });
}

// 异步遍历器写法
async function main(inputFilePath) {
  const readStream = fs.createReadStream(
    inputFilePath,
    { encoding: 'utf8', highWaterMark: 1024 }
  );

  for await (const chunk of readStream) {
    console.log('>>> '+chunk);
  }
  console.log('### DONE ###');
}

传统方式使用事件的形式来传输数据,而异步遍历器的写法更加的直观,可以很明显的看出不仅仅代码简洁了,流程也更加的清楚明了。

asyncIterator 与 async Generator

asyncIteratorasync Generator 的关系和 IteratorGenerator 的关系是一致的,前者规定了一个接口的形式,后者为前者产出规定形式的接口。而在异步操作中,我们一般不会自己去写符合 asyncIterator 的函数,直接使用 async Generator 更方便快捷。

一个简单的例子

function createPromise(params){
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(params)
        }, 1000)
    })
}

let ag = async function* gen(){
    yield createPromise('hello')
    yield createPromise('world')
}

let obj = {
    arr: ['world', 'hello'],
    [Symbol.asyncIterator]: ag
}

async function f() {
    for await (const x of obj) {
        console.log(x);
    }
}

f();
// hello
// world

将这段代码运行,可以发现 1 秒后输出 hello 接着 1 秒后输出 world 。不仅代码简洁,而且执行顺序也是我们想要的。

总结

asyncIteratorasync Generator 的配合使用使我们能更好的去控制有顺序的且结构一致的异步循环,但我们始终要明确一点:这一切都是基于 Promise 实现了,不论流程上多么的清晰明了,这和 async/await 一样,都仅仅是一块语法糖,javascript 中是不存在正真的异步同步化,这点需要牢记。

Read more

Gitlab 搭建

Gitlab 搭建

为什么? 想要自己搭建一个代码仓库无非是以下几点原因: 1. 公司内部项目 2. 自己的项目,但不适合在公网 3. 大部分的 git 仓库虽然有私有服务,但价格都不便宜,甚至不如一台云服务器来的便宜 配置及安装文档 Gitlab * 由于 gitlab 会用到 22 端口端口转发的化就走不了 git clone 的默认配置,且占用内存较高,不推荐使用 docker 进行部署; * 由于 gitlab 自带 nginx 默认情况下会与属主机的 nginx 冲突,因此推荐只使用 gitlab 自带的 nginx 进行端口转发; 最小化配置 # path /etc/gitlab/gitlab.rb external_url 'http://git.

By breeze
NPM 私服

NPM 私服

verdaccio 私服搭建,搭建过程中的一些问题以及解决: * docker compose 启动后,可能会报错:配置找不到,添加 config.yaml 文件到映射的 ./conf 目录即可,config.yaml 具体内容可在官网找到,下方也有最小化配置。 services: verdaccio: image: verdaccio/verdaccio container_name: 'verdaccio' networks: - node-network environment: - VERDACCIO_PORT=4873 - VERDACCIO_PUBLIC_URL=http://npm.demo.com ports: - '10001:4873'

By breeze