温故知新之ES6(五)

1. Iterator 和 for...of 循环

ES6 中有四种数据集合:数组( Array )、对象( Object )、Map 和 Set 。这样就需要一种统一的接口机制,来处理所有不同的数据结构。
遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署Iterator接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。

1.1 Iteration作用:

1. 为各种数据结构,提供一个统一的、简便的访问接口;
2. 使得数据结构的成员能够按某种次序排列;
3. ES6创造了一种新的遍历命令 for...of 循环,Iterator接口主要供 for...of 消费。

1.2 Iterator 的遍历过程

(1)创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。

(2)第一次调用指针对象的 next 方法,可以将指针指向数据结构的第一个成员。

(3)第二次调用指针对象的 next 方法,指针就指向数据结构的第二个成员。

(4)不断调用指针对象的 next 方法,直到它指向数据结构的结束位置。

ES6 规定,默认的 Iterator 接口部署在数据结构的 Symbol.iterator 属性,或者说,一个数据结构只要具有 Symbol.iterator 属性,就可以认为是“可遍历的”(iterable)。

原生具备 Iterator 接口的数据结构如下。

Array
Map
Set
String
TypedArray
函数的 arguments 对象
NodeList 对象

1.3 调用 Iterator 接口的场合

1)解构赋值

对数组和 Set 结构进行解构赋值时,会默认调用 Symbol.iterator 方法。

let set = new Set().add('a').add('b').add('c');
let [x,y] = set;
// x='a'; y='b'

let [first, ...rest] = set;
// first='a'; rest=['b','c'];
2)扩展运算符

扩展运算符(...)也会调用默认的 Iterator 接口。

// 例一
var str = 'hello';
[...str] //  ['h','e','l','l','o']

// 例二
let arr = ['b', 'c'];
['a', ...arr, 'd']
// ['a', 'b', 'c', 'd']

只要某个数据结构部署了 Iterator 接口,就可以对它使用扩展运算符,将其转为数组。

3)yield*

yield* 后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口。

let generator = function* () {
  yield 1;
  yield* [2,3,4];
  yield 5;
};

var iterator = generator();

iterator.next() // { value: 1, done: false }
iterator.next() // { value: 2, done: false }
iterator.next() // { value: 3, done: false }
iterator.next() // { value: 4, done: false }
iterator.next() // { value: 5, done: false }
iterator.next() // { value: undefined, done: true }

4)其他场合

由于数组的遍历会调用遍历器接口,所以任何接受数组作为参数的场合,其实都调用了遍历器接口。下面是一些例子。

for...of
Array.from()
Map(), 
Set(), 
WeakMap(), 
WeakSet()(比如 new Map([['a',1],['b',2]]) )
Promise.all()
Promise.race()

2 Generator

Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同。

Generator

1. 语法上: 
    Generator 函数是一个状态机,封装了多个内部状态。Generator 函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。
2. 形式上: 
    Generator 函数是一个普通函数,但是有两个特征:
    一是,function关键字与函数名之间有一个星号;
    二是,函数体内部使用yield表达式,定义不同的内部状态(yield在英语里的意思就是“产出”)。

可以参看阮一峰老师的--Generator 函数的语法

3. async 函数

用过的人都说好

ES2017 标准引入了 async 函数,使得异步操作变得更加方便

async 函数是什么?
一句话,它就是 Generator 函数的语法糖。

async 函数就是将 Generator 函数的星号( * )替换成 async ,将 yield 替换成 await ,仅此而已。

3.1 async 的改进

async 函数对 Generator 函数的改进,体现在以下四点:

1. 内置执行器。

Generator 函数的执行必须靠执行器,所以才有了 co 模块,而 async 函数自带执行器。也就是说, async 函数的执行,与普通函数一模一样,只要一行。

asyncReadFile();

上面的代码调用了 asyncReadFile 函数,然后它就会自动执行,输出最后结果。这完全不像 Generator 函数,需要调用 next 方法,或者用 co 模块,才能真正执行,得到最后结果。

2. 更好的语义。

async 和 await ,比起星号和 yield ,语义更清楚了。 async 表示函数里有异步操作, await 表示紧跟在后面的表达式需要等待结果。

3. 更广的适用性。

co模块约定, yield 命令后面只能是 Thunk 函数或 Promise 对象,而 async 函数的 await 命令后面,可以是Promise 对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)。

4. 返回值是 Promise。

async 函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。你可以用 then 方法指定下一步的操作。

进一步说,async 函数完全可以看作多个异步操作,包装成的一个 Promise 对象,而 await 命令就是内部 then 命令的语法糖

1_ko3KtcVSlzpe3RnTRgJaHw (1).jpeg

3.2 基本语法

1. 例子
  1. async 函数返回一个 Promise 对象。
  2. await 命令,正常情况下, await 命令后面是一个 Promise 对象。如果不是,会被转成一个立即 resolve 的 Promise 对象。

// 如下代码,三种情况加上捕获异常,应该很容易理解

asyncDemo();
async function asyncDemo() {
    async function sleep(para) {
        return new Promise(function(resolve, reject) {
            setTimeout(function() {
                resolve(para * para);
            }, 1000);
        });
    }

    try {
        const result = await sleep(2);
        console.log(result);

        // async使用 返回一个promise
        async function asyncSleep(para) {
            // 等待得到最终状态后返回
            return await sleep(para);
        }
        // await 使用
        const result_1 = await asyncSleep(3);
        console.log(result_1);

        asyncSleep(4).then(function(result_2) {
            console.log(result_2);
            return Promise.reject("异常")
        });
    } catch (err) {
        // 捕获异常
        console.log(err);

    }
}

答案:

4
9
16
(node:20766) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 2): 异常

3.3 使用注意点

  1. await 命令后面的 Promise 对象,运行结果可能是 rejected ,所以最好把 await 命令放在 try...catch 代码块中。
  2. 多个 await 命令后面的异步操作,如果不存在继发关系,最好让它们同时触发。

let foo = await getFoo();
let bar = await getBar();

// 写法 1
let [foo, bar] = await Promise.all([getFoo(), getBar()]);

// 写法 1.1 
Promise.all([
    new Promise<any>(async (v, e) => v(await test_1().catch(err => e(err)))),
    new Promise<any>(async (v, e) => v(await test_2().catch(err => e(err)))),

])


// 写法二
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;

  1. await 命令只能用在 async 函数之中,如果用在普通函数,就会报错。

3.4 async 函数的实现原理

async 函数的实现原理,就是将** Generator 函数和自动执行器,包装在一个函数里。**

async function fn(args) {
  // ...
}
// 等同于
function fn(args) {
  return spawn(function* () {
    // ...
  });
}

所有的 async 函数都可以写成上面的第二种形式,其中的 spawn 函数就是自动执行器。

下面给出 spawn 函数的实现,基本就是前文自动执行器的翻版。

function spawn(genF) {
  return new Promise(function(resolve, reject) {
    var gen = genF();
    function step(nextF) {
      try {
        var next = nextF();
      } catch(e) {
        return reject(e);
      }
      if(next.done) {
        return resolve(next.value);
      }
Promise.resolve(next.value).then(function(v) {
        step(function() { return gen.next(v); });
      }, function(e) {
        step(function() { return gen.throw(e); });
      });
    }
    step(function() { return gen.next(undefined); });
  });
}

更多使用方法,根据业务场景具体分析吧
比如:错误处理、并行执行

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

推荐阅读更多精彩内容