前端Tips#6 - 在 async iterator 上使用 for-await-of 语法糖

题图

视频讲解

前往原文 前端Tips 专栏#6,点击观看

文字讲解

本期主要是讲解如何使用 for-await-of 语法糖进行异步操作迭代,让组织异步操作的代码更加简洁易读。

1、场景简述

以下代码中的 for...of 操作,打印顺序 "2、3、4"(总共耗费时间 4s):

const delay = (time) => () => setTimeout(() => { console.log(time) }, time * 1000);

const delays = [delay(3), delay(2), delay(4)];

for (cur of delays) {
    cur();
}

但我们想要以数组顺序打印 “3、2、4”(总共耗时9s),请问该如何实现?

2、同步迭代器

以常见的数组打印为例,下述代码会依次打印出 "0、1":

for(const cur of [0, 1]){
    console.log(cur);
}

那么如何用 同步迭代器 实现上述同等输出?

Iterator 是 ECMAScript 2015 引进的功能,它就是一个 function,只不过对这个 function 的形式有特殊的规定:

  1. 返回对象必须包含 next 属性,该属性也是 function
  2. next 函数返回值必须返回包含 donevalue 这两个字段的对象

有了 Iterator,就可以借助 [Symbol.iterator] 构造出 可迭代对象(Iteratable)

// 返回一个可迭代对象,注意 [Symbol.iterator] 这个 key 
const someIteratable = {
    [Symbol.iterator]: someIterator 
}

凡是可迭代对象就可以使用 for...of 语法,所以这是一种层层迭进的关系。

3、使用迭代器实现数组打印

知道了迭代器的概念后,就可以借助迭代器实现上述的数组打印功能,首先自定义构造出 countIterator 迭代器

let count = 0;
function countIterator() {
    // 返回一个迭代器对象,对象的属性是一个 next 方法
    return {
        next: function () {
            if (count < 2) {
                // 当没有到达末尾时,返回当前值,并把索引加1
                return { value: count++, done: false };
            }

            // 到达末尾,done 属性为 true
            return { value: count, done: true };
        }
    };
}

然后创建出可迭代对象,由于该对象的行为和 [0,1] 这个数组类似,所以起名为 customArray

// 返回一个可迭代对象,注意 [Symbol.iterator] 这个 key 
const customArray = {
    [Symbol.iterator]: countIterator 
}

最后给这个可迭代对象应用 for...of 即可,就能打印出 0、1 内容:

for (const cur of customArray) {
    console.log(cur)
}

通过这个例子你就应该比较容易迭代器的理解,其实 JS 原生的StringArrayMapSet 等都是可迭代对象,因为它们的原型对象都有一个 Symbol.iterator 方法

4、异步迭代器

理解了同步迭代器,那么 异步迭代器(Async Iterator)也就很容易理解了,它和同步迭代器的差别在于:

  1. 异步迭代器必须返回 Promise 对象,且该 Promise 返回 { value, done } 格式对象
  2. 异步可迭代对象(Async Iteratable)用 Symbol.asyncIterator 作为 key
  3. 异步可迭代对象(Async Iteratable)可用 for-await-of 进行迭代

Async iterator 是 ECMAScript 2018 引进的

借助异步迭代器就可以实现本期开头所讲的功能,实现自定义的 delayIteraterable 可迭代对象,它使用 [Symbol.asyncIterator] 作为 key,其 value 就是异步迭代器:

const promisify = func => (...args) =>
    new Promise((resolve, reject) =>
        func(...args, (err, result) => (err ? reject(err) : resolve(result)))
    );

const delayIteraterable = {
    [Symbol.asyncIterator]: () => {
        return {
            next: () => {
                const cur = promisify(delays.shift());
                return cur().then(res => {
                    return {
                        done: delays.length === 0,
                        value: res
                    }
                });
            }
        }
    }
}

这里用到的 promisify 函数,具体可参考前端 Tips - 第 5 期的内容讲解。

然后直接搭配 for-await-of 语法糖使用,就能进行异步迭代,按我们的要求依次输出 “3、2、4”(总共耗时9s)

const execIt = async function () {
    for await (const cur of delayIteraterable) {
        console.log(cur);
    }
}
execIt();

5、扩展:Generator & Async Generator

除了用迭代器生成 可迭代对象 外,还能用 Generator(生成器)生成 可迭代对象,而且一般来讲代码实现也更为紧凑。

由于时间关系就不展开了,感兴趣的可阅读文末的参考文章自行学习。本期的例子也提供了 generator 的版本可供参考,链接:https://github.com/boycgit/fe-program-tips/blob/master/src/6-async-iterator/async-yield.js

6、参考文档

附:如何获取往期 “前端Tips” 列表

有两种方式获取历史 tips:

① 在公众号内回“tips” + “年份” + “A(或者B)” 获取半年度 tips。例如:回复 “tips2020A” 即可获取 2020 年上半年 tips 列表

② 前往网站:https://boycgit.github.io/fe-program-tips ,里面提供了搜索功能

欢迎大家关注我的知识专栏,更多内容等你来挖掘

「可在微信内搜索 “JSCON简时空”或 “iJSCON” 关注」

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,591评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,448评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,823评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,204评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,228评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,190评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,078评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,923评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,334评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,550评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,727评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,428评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,022评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,672评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,826评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,734评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,619评论 2 354

推荐阅读更多精彩内容