Iterator、Generator、async、await 异步编程
Iterator 遍历器
说明
Iterator(遍历器、迭代器)是一种接口,他为不同的数据结构提供了统一的访问机制(即 for-of循环),任何数据结构只要部署 Iterator=接口,就可以完成遍历操作(即一次处理该数据结构的所有成员)
主要作用
- 为各种数据结构提供统一、简便的访问借口
- 使得数据结构的成员能够按照某种次序排列
- 提供给 for-of 循环使用
一个基本的迭代器的构成
从规范的遍历过程来看
- 迭代器对象创建时,实际是创建一个指针对象,指向当前数据结构的起始位置
- 第一次调用迭代器对象的 next() 方法,指针指向数据结构的第一个成员
- 不断调用 next() 方法 指针依次指向下一个成员,直到指向数据结构的末尾
每一次调用 next() 方法都会返回当前成员的信息,包括 value:值;done:true|false 表示遍历是否结束
const a = [12, 223, 34, 54];
const iter = convertToIterator(a);
console.log(iter.next()); // {value: 12, done: false}
console.log(iter.next()); // {value: 223, done: false}
console.log(iter.next()); // {value: 34, done: false}
console.log(iter.next()); // {value: 54, done: false}
console.log(iter.next()); // {value: undefined, done: true}
function convertToIterator(arr) {
let idx = 0;
return {
next: function () {
return idx < arr.length ?
{value: arr[idx++], done: false} :
{value: undefined, done: true};
}
}
}
默认的 Iterator 接口
一种数据结构只要部署了 Iterator 接口,就可以成为‘可遍历的’, ES6 的 Iterator 接口部署在数据结构的
Symbol.iterator
上,这个属性本省是一个函数,调用这个函数就会返回一个遍历器对象,遍历器对象具有 next() 方法控制台查看查看
console.log(Array.prototype);
Symbol 对象简介
ES5 中对象的属性名都是字符串,如果给对象添加新方法的名称与旧的相同,就会将之前的属性覆盖掉,Symbol 是 ES6 引入的一种新的原始数据类型(有点类似于字符串的数据类型),表示独一无二的值
Symbol.iterator
是内置的Symbol 值(Symbol(Symbol.iterator)
)指向对象的默认遍历器更多 Symbol 资料查看这里
基本结构
const obj = {
[Symbol.iterator] : function () {
return {
next: function () {
return {
value: 1,
done: true
};
}
};
}
};
具备 Iterator 接口的数据结构
Array | Map | Set | String | TypedArray | 函数的 arguments
调用 Iterator 接口的方法
for-of | 扩展运算符... | 解构赋值 | Array.form
let arr = ['a', 'b', 'c'];
let iter = arr[Symbol.iterator]();
iter.next() // { value: 'a', done: false }
iter.next() // { value: 'b', done: false }
iter.next() // { value: 'c', done: false }
iter.next() // { value: undefined, done: true }
Generator 函数
Generator 函数是 ES6 提供的一种异步编程的解决方案,语法行为与传统函数完全不同,可以将其理解为一个状态机,内部封装了多个内部状态,执行 Generator 函数会返回一个遍历器对象,返回的遍历器对象可以依次遍历 Generator 函数内部的每一个状态
一个 Generator 函数的基本构成
与普通函数相比
- Generator 函数
function
关键字后要加*
function* name(){}
; - 函数体内部使用
yield
表达式,定义不同的内部状态,最后一个 return 表示返回最后一个状态 - Generator 函数调用与普通函数一样,不同的是 Generator 函数调用后并不执行,返回的也不是函数结果,而是一个指向内部状态的指针,也就是遍历器对象
- 每次通过调用遍历器对象的next() 方法,使指针移动到下一个状态,直到最后一个 return 的状态
- Generator 函数是分段执行的, yield 表达式是暂停执行的标记,而 next 方法可以恢复执行
function* generator () {
yield 'first';
yield 'sec';
return 'finish';
}
const a = generator();
console.log(a);
console.log(a.next()); // {value: "first", done: false}
console.log(a.next()); // {value: "sec", done: false}
console.log(a.next()); // {value: "finish", done: true}
console.log(a.next()); // {value: undefined, done: true}
yield 表达式
yield 表达式是 Generator 函数内部的暂停标志
- 调用 Generator 函数,函数体内代码不会执行
- 调用 next() 方法后,函数体内代码顺序执行,直到遇到 yield 表达式(
yield 111 + 1;
),执行完此表达式,表达式的值就是返回对象的 value - yield 表达式与 return 相似,都能返回后边表达式的值,return 在一个函数里只能执行一次,yield 可以执行很多次,
- yield 表达式(a)如果在另外一个表达式中(b),必须放在括号中,而且 yield 表达式a 执行后的返回值与表达式 b 暂时不会执行,会等到下一次 next 在执行
function* generator () {
console.log('1');
yield 111 + 1;
console.log('2');
yield 222 + 2;
console.log('3');
return 33 + 3;
}
const a = generator(); // 什么都没有打印
console.log(a);
const fir = a.next(); // 1
console.log(fir); // {value: 112, done: false}
a.next(); // 2
next 方法的参数
yield 表达式本身没有返回值,或者说总是返回 undefined next 方法可以带一个参数,此参数会被当做 上一个 yield 表达式的返回值,一般第一次调用 next()方法不会输入值,只有第二次传递的参数才是有效的
function* gerenator() {
const x = 30;
const a = yield x;
return a + 10;
}
const test = gerenator();
const val = test.next().value;
const final = test.next(val + 50);
console.log(final); // {value: 90, done: true}
for-of 循环
for…of循环可以自动遍历 Generator 函数时生成的Iterator对象,且此时不再需要调用next方法
除了for…of循环以外,扩展运算符(…)、解构赋值和Array.from方法内部调用的,都是遍历器接口。这意味着,它们都可以将 Generator 函数返回的 Iterator 对象,作为参数
function* gerenator() {
const x = 30;
const a = yield x;
const b = yield a;
return b + 10;
}
const test = gerenator();
// for-of 循环
for (let o of test) {
console.log(o);
// 30 undefined
}
// 展开操作符
console.log([...test]); // [30, undefined]
// Array.from()
console.log(Array.from(test)); // [30, undefined]
// 解构赋值
let [a, b] = test;
console.log(a, b); // 30 undefined
Generator.prototype.throw()
Generator 函数的遍历器对象都由一个 throw() 方法,可以在函数体外抛出错误,然后在函数体内捕获
Generator 函数体外抛出的错误,可以在函数体内捕获;反过来,Generator 函数体内抛出的错误,也可以被函数体外的catch捕获。
var g = function* () {
try {
yield;
} catch (e) {
console.log('内部捕获', e);
}
};
var i = g();
i.next();
try {
i.throw('a');
i.throw('b');
} catch (e) {
console.log('外部捕获', e);
}
// 内部捕获 a
// 外部捕获 b
Generator.prototype.return()
return 方法 用来终结遍历 Generator 函数,如果 return() 有参数,则返回参数的值
function* gen() {
yield 1;
yield 2;
yield 3;
}
var g = gen();
console.log(g.next()); // { value: 1, done: false }
console.log(g.return('foo')); // { value: "foo", done: true }
console.log(g.return('foo')); // { value: undefined, done: true }
console.log(g.next()); // { value: undefined, done: true }
function* test() {
yield 1;
yield 2;
yield 3;
}
var t = test();
console.log(t.next()); // { value: 1, done: false }
console.log(t.return()); // { value: undefined, done: true }
yield* 表达式
用于在一个 Generator 函数内部调用另外一个 Generator 表达式
- Generator 函数内部直接执行
foo()
外部函数不会得到返回值 - Generator 函数内部执行
yield foo()
外部函数会得到一个遍历器对象 - Generator 函数内部执行
yield* foo()
外部函数会使用这个遍历器
function* foo() {
yield 'a';
yield 'b';
}
function* bar1() {
yield 'x';
yield foo();
yield 'y';
}
for (let o of bar1()) {
console.log(o);
/*
x
Generator {_invoke: function}
y
*/
}
function* bar2() {
yield 'x';
yield* foo();
yield 'y';
}
for (let o of bar2()) {
console.log(o);
/*
x
a
b
y
*/
}
作为对象属性的 Generator 函数
// 简写
let obj = {
* myGeneratorMethod() {
}
};
// 等价于
let obj = {
myGeneratorMethod: function* () {
}
};
注意事项
- Generator 函数不是构造函数 不能使用 new 关键字创建对象
- Generator 函数总是返回一个遍历器,而不是一般构造函数的 this 不可以像构造函数一样在函数内部 通过 this 定义属性,但是可以在 函数的 prototype 上定义方法
javascript 传统的异步编程
异步就是指一个任务不是连续完成的,先执行一部分,转而做其他任务,等到做好准备再回来执行接下的部分
- 回调函数
- 事件监听
- 发布/订阅
- Promise
协程
‘协程’的意思是多个线程相互协作,大致流程如下
- 协程 A 开始执行
- 协程 A 执行到一半,进入暂停,执行权转移到协程 B
- 一段时间后 协程 B 交还执行权给 A
- 协程 A 继续执行
Generator 函数是携程在 ES6 的实现,最大的特点就是可以交出函数的执行权,整个 Generator 函数就是一个封装的异步任务,或者说是异步任务的容器,异步操作需要暂停的地方都用 yield 语句注明
function change() {
return 'change';
}
function* test() {
const a = yield change(); // 执行权交给 change 函数
yield a + ' something';
}
const ts = test();
const ret = ts.next();
console.log(ret.value); // change
console.log(ts.next(ret.value).value); // change something
Generator 函数的数据交换和错误处理
- 向外输出数据 通过 next() 返回值的 value 属性
- 向内注入数据 通过 next(sth) 方法添加参数
- Generator 函数内可以部署错误处理代码,捕获函数体外抛出的错误(generator.throw())
async 函数
async 函数由 ES2017 引入,为了更加方便异步操作,它其实就是 Generator 函数的语法糖
async 函数 与 Generator 函数的差异
- 内置执行器,Generator 函数执行必须靠执行器或者调用 next() 方法,而 async 函数自带执行器,async 函数的调用与普通函数一样
- 更好的语义:显而易见 async(异步)await(等待)这样的组合更容易表示
- await 命令后边可以是 Promise 对象或者原始类型的值(原始类型的值,会等同于同步操作)
- async 函数返回值是 Promise 类型的可以用 then 方法指定下一步操作
async function timeout(ms) {
await new Promise(function (resolve) {
setTimeout(resolve, ms);
})
}
async function asyncPrint (value, ms) {
await timeout(ms);
console.log(value);
}
asyncPrint('hahaha', 1000);
async 函数的多种使用形式
// 函数声明
async function fn () {}
// 函数表达式
const fn = async function () {}
// 对象的方法
const obj = {
async fn () {}
}
// Class 方法
class Fn {
async getFn() {
}
}
// 箭头函数
const fn = async () => {};
async 函数的返回值
async 函数返回一个 Promise 对象,这个对象可以调用 then() 方法 进行下一步的操作,但是如果需要上一步的返回值则需要在 async 函数内 return
async函数内部抛出错误,会导致返回的 Promise 对象变为reject状态。抛出的错误对象会被catch方法回调函数接收到
const obj = {
name: 'obj name',
age: 'obj age',
async timeout(ms) {
const age = await new Promise((resolve) => {
setTimeout(() => {
resolve(this.age);
}, ms)
})
// 返回给下一步的参数
return this.name + ' ' + this.age;
}
}
async function asyncPrint (value, ms) {
const info = await obj.timeout(ms);
console.log(value, info);
// 抛出错误
throw new Error('wrong');
}
asyncPrint('hahaha', 1000) // hahaha obj name obj age
.then(res => {
console.log('right', res);
})
.catch(err => {
console.log(err); // 错误被捕获 Error: wrong
})
一个 async 函数返回的 Promise 对象,必须等到内部素有的 await 执行完成后,才会发生状态改变,除非遇到 return 语句 或者抛出错误
await 命令
- 正常情况下 await 命令后是一个 Promise 对象,如果不是,会被转变成一个立即 resolve 的 Promise 对象
- await 命令后边的 Promise 对象如果为 reject 状态,则 reject 的参数会被 catch 方法的回调函数接收
- 只要一个 await 语句后面的 Promise 变为 reject 那么整个 async 函数都会中断执行
- 通过 try-catch 结构可以避免一个异步操作失败影响后边异步操作执行
// 1. 正常情况下 await 命令后是一个 Promise 对象,如果不是,会被转变成一个立即 resolve 的 Promise 对象
async function fn() {
return await 222;
}
fn().then(res => console.log(res)); // 222;
// 2. await 命令后边的 Promise 对象如果为 reject 状态,则 reject 的参数会被 catch 方法的回调函数接收
// 3. 只要一个 await 语句后面的 Promise 变为 reject 那么整个 async 函数都会中断执行
async function fn() {
await Promise.reject('wrong');
return await 222; // 不会执行
}
fn()
.then(res => console.log(res)) // 不执行
.catch(err => console.log('error', err)); // error wrong
// 4. 通过 try-catch 结构可以避免一个异步操作失败影响后边异步操作执行
async function fn() {
try {
await Promise.reject('wrong');
} catch(e) {
console.log('catch error', e); // catch error wrong
}
return await 222;
}
fn()
.then(res => console.log(res)) // 222;
.catch(err => console.log('error', err)); // 不执行1
// 在可能会抛出错误的Promise 后边接上 catch() 也可以达到同样的效果
async function fn() {
await Promise.reject('wrong').catch(err => console.log('catch', err)); // catch wrong
return await 222;
}
fn()
.then(res => console.log(res)) // 222;
.catch(err => console.log('error', err)); // 不执行
注意事项
1. 如果了两个异步操作没有互相依赖关系,可以让他们同时触发
// 写法一
let [foo, bar] = await Promise.all([getFoo(), getBar()]);
// 写法二
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;
2. await 命令只能用在 async 函数中,如果 用在普通函数中就会报错
async function fn() {
const arr = [11, 232, 33];
arr.forEach((item) => {
await new Promise((resolve) => {
resolve(item)
})
})
return await 222;
}
fn()
.then(res => console.log(res)) // 报错