第六章 :Promise & Async/await & Generators—下-灵析社区

懒人学前端

二、简单介绍下 ES6 中的 Iterator 和 Iterable

JavaScript 提供对数据迭代的协议。迭代协议具体分为两个协议:

  • 可迭代协议(iterable protocol)
  • 迭代器协议(iterator protocol)

可迭代协议

可迭代协议允许 JavaScript 对象定义或定制它们的迭代行为,例如,在一个 for..of 结构中,哪些值可以被遍历到。

要成为可迭代对象,该对象必须实现 @@iterator 方法,可通过常量 [Symbol.iterator] 访问该属性,它的返回值为一个符合迭代器协议的对象。

迭代器协议

[Symbol.iterator] 返回的对象就是一个迭代器对象。它提供 next() 方法,返回一个具有以下属性的对象:

  • value:迭代器返回的任何 JavaScript 值。done 为 true 时可省略。
  • done:迭代器能否返回下一个值,不能则为 false, 否则为 true

我们可以这样理解,一个可迭代对象实现了一个接口,询问两个问题:

  • 还有剩余的元素吗?
  • 如果有,剩余的元素是什么?

内置的可迭代对象

  • StringArrayTypedArrayMapSet 都是内置的可迭代对象,因为它们的每个 prototype 对象都实现了 @@iterator方法。
  • arguments 对象和一些 DOM 集合类型,如 NodeList 也是可迭代的。目前,没有内置的异步可迭代对象.
  • 生成器函数(generator)返回生成器对象,它们是可迭代的迭代器。

对象是否可迭代?

Object 是不可迭代的,如下面的代码:

const people = {
  name: 'John Doe',
  age: 13,
  sex: 'male',
  born: 1994
};

for (const p of people) {
  console.log(p); // Uncaught TypeError: people is not iterable
}

我们可以给对象添加 Symbol.iterator,使其可迭代,但这个不是最佳方案。我们可以使用 Object.entires() 或使用 Map。

Object.entires()

Object.entries(people); // [['name', 'John Doe'], ['age', 13], ['sex', 'male'], ['born', 1994]]

Map

const people = {
  name: 'John Doe',
  age: 13,
  sex: 'male',
  born: 1994
};

const buildMap = obj =>
  Object.keys(obj).reduce((map, key) => map.set(key, obj[key]), new Map());

const map = buildMap(people);

map.get('name') // John Doe
map.get('age'); // 13

实现一个迭代器

由上可知,实现一个迭代器,我们要做两步:

  • 实现可迭代协议,如下面代码 A 处。
  • 实现迭代器协议,如下面代码 B 处。
class MyIterator {
  constructor(params) {
    this.index = 0;
    this.value = params;
  }
  [Symbol.iterator]() {
    return this; // A
  }
  next() { // B
    return {
      value: this.value[this.index++],
      done: this.index > this.value.length ? true : false,
    };
  }
}

let it = new MyIterator(["a", "b", "c"]);
console.log(it.next()); // { value: "a", done: false }
console.log(it.next()); // { value: "b", done: false }
console.log(it.next()); // { value: "c", done: false }
console.log(it.next()); // { value: undefined, done: true }
console.log(it.next()); // { value: undefined, done: true }

小练习

手写一个迭代器。

答案:见上文。

三、谈谈对生成器(Generator)的理解

常规函数只会返回一个单一值(或者不返回值),而 ES6 的generator 可以按需一个接一个地返回(yield)多个值。

JavaScript 中的函数是不可暂停的,generator 函数则可以暂停:

function* generate() {
	console.log("invoked 1st time"); // "invoked 1st time"
	yield 1;
	console.log("invoked 2nd time"); // // "invoked 2nd time"
	yield 2;
}
let gen = generate();
console.log(gen); // Object [Generator] {}

let result = gen.next(); 
console.log("result", result); // {value: 1, done: false}

我们分析一下上面的代码:

  • * 号在函数前面,代表这是个 generator 函数
  • yield 表达式返回一个值,并且暂停这个函数的执行
  • generate() 调用后,返回一个 Generator 对象,还没有执行它的函数体的部分。
  • 调用 Generator 对象上的 next() 方法,执行函数体的内容,遇到 yield 返回 1 并且暂停 generator 函数。

next() 方法

generator 的主要方法就是 next(),它是一个具有两个属性的对象:

  • value: 产出的(yielded)的值。
  • done: 如果 generator 函数已执行完成则为 true,否则为 false

yield

当调用 next() 方法的时候,yield 将返回值添加到对象的 value 属性上。

function* foo() { 
    yield 1;
    yield 2;
    yield 3;
}

let f = foo();

console.log(f.next()); // { value: 1, done: false }

yield*

yield* 表达式迭代操作数,并产生它返回的每个值。我们可以使用 yield* 返回数组的每个元素:

function* yieldArrayElements() {
    yield 1;
    yield* [ 20, 30, 40 ];
}

let a = yieldArrayElements();

console.log(a.next()); // { value: 1, done: false }
console.log(a.next()); // { value: 20, done: false }
console.log(a.next()); // { value: 30, done: false }
console.log(a.next()); // { value: 40, done: false }

generator 是可迭代的

ES6 中 for...of 可以遍历:

  • ArrayStringMap
  • Array-like 对象,比如 arguments 或者 NodeList
  • 用户定义的对象,实现了可迭代接口的。
let str = 'abc';
for (let c of str) {
    console.log(c);
}

输出结果如下:

同样,我们也可以使用 for...of 循环遍历 generator 所有的值。如下示例:

function* generate() {
	console.log("invoked 1st time"); // "invoked 1st time"
	yield 1;
	console.log("invoked 2nd time"); // "invoked 2nd time"
	yield 2;
}
let gen = generate();
console.log(gen); // Object [Generator] {}

// for...of
for (const g of gen) {
	console.log("for...of g", g); 
}

输出如下图:

小练习

下面代码的输出结果是什么?

const arr = ['a', 'b', 'c'];

function* generator() {
    yield 1;
    yield* arr;
    yield 2;
}

for (let value of generator()) {
    console.log(value)
}

答案:这里的考点是 yield*,会输出数组每个元素。

四、介绍一下 async/await

async/await 定义

async/await 实际上是 Promise 的语法糖,它可以用一种更舒适的方式使用 Promise,更易于理解和使用。

async 关键字放在函数前面,表达这个函数总是返回一个 Promise,其他值将自动被包装在一个 resolved 的 Promise 中。

async function f() {
    return 1;
}

f().then((v) => console.log(v));

console.log('f', f())

结果如下图:

await 关键字可以暂停函数的执行,直到 Promise 的状态变成已敲定,然后 Promise 的结果继续执行。相比于 promise.then,它显得更为优雅。如果在非 async 中使用,会报语法错误。

async function f() {

  let promise = new Promise((resolve, reject) => {
    setTimeout(() => resolve("done!"), 1000)
  });

  let result = await promise; // 等待,直到 promise resolve (*)

  alert(result); // "done!"
}

f();

错误处理

如果一个 Promise 正常 resolve,await promise 的结果就是其返回值,否则,将抛出错误。我们可以使用 try/catch 来捕获错误。

async function f() {
  try {
    let response = await new Promise((resolve, reject) => {
        throw new Error('test err')
    });
  } catch(err) {
    console.log(err); // Error: test err
  }
}

f();

小练习

如何使用 async 实现 delay 函数, delay 函数定义如下:

delay((str) => str, 3000, 'Hello world').then(res => console.log(res))

答案:参考代码如下:

const sleep = (cb, time, args) => ({
	then: (cb) => {
		setTimeout(() => cb(args), time);
	}
});

const delay = async (cb, time, args) => {
	return await sleep(cb, time, args);
};

delay((str) => str, 3000, "Hello world").then((res) => console.log(res)); // Hello world

五、如何实现红绿灯效果?

红绿灯题目:实现一个信号灯,这个信号灯,有黄绿红,他们各自亮灯的持续时间是 1s,2s,3s 如此反复。

这道题考查的是对 Promise async/await 的应用。

function delay(fn, time) {
	return new Promise((resolve) => {
		setTimeout(() => {
			resolve(fn()); // A
		}, time);
	});
}

async function light() {
	await delay(() => console.log("red"), 3000);
	await delay(() => console.log("green"), 2000);
	await delay(() => console.log("yellow"), 1000);
	await light();
}

light();
  • light() 是一个带有 async 关键字的函数,代表这是个异步函数。
  • await 函数接收一个 Promise,并且等待 Promise resolve 之后再继续执行后续代码。
  • delay() 函数是我们创建的一个返回 Promise 的函数,这个函数在指定时间后异步调用回调函数,注意代码 A 处.

阅读量:1224

点赞量:0

收藏量:0