生如夏花之绚烂,死如秋叶之静美
说明
学习总结 + 个人理解,巩固 + 方便查阅,大家愿意看的简单看看就好
Promise
真的就是最好了?
在上一篇《你不知道的JS系列——深入理解Promise》,我们看到了 Promise
相对于回调表达程序异步所体现出来的优越性:顺序性和可信任性,但 Promise
就是最好了吗?
Promise
的最大问题是代码冗余,原来的任务被 Promise
包装了一下,不管什么操作,一眼看去都是一堆 then
,原来的语义变得很不清楚。
那么有没有一种以顺序的、同步的来表达异步的方式,因为这更符合我们大脑的思考模式,此时不简单的生成器(Generator
)登场了。直接说吧,ES6
中 最完美的世界 就是生成器(看似同步的异步代码)和 Promise
(可信任可组合)的结合。我们平时应用最多的 async/await
就是它们的组合实现。
Tips: 生成器与遍历器具有不可分割的关系,要了解生成器,那么首先得需要了解遍历器。
什么是 Iterator
(遍历器)?
Iterator
既称迭代器也称遍历器,都表达的是一个意思。它是一种统一的接口机制,用来处理所有不同的数据结构。任何数据结构只要部署了 Iterator
接口,就可以完成遍历操作,这个接口主要供 for...of
消费。
Iterator
遍历器对象,主要具有 next
方法,还可以具有 return
方法和 throw
方法,其中 return
方法和 throw
方法并不是必须的。
一个手写的 Iterator
遍历器对象大致如下:
{
next: function() {
return { value: '某个值', done: true}; // done 是布尔值,代表遍历是否结束
},
return: function() {
file.close(); // 可以在这里清理或释放资源
return { value: '某个值', done: true };
},
throw: function() {
// 用于在函数体外调用抛出错误
}
}
复制代码
一般我们并不会手写 Iterator
遍历器,多是用 Generator
函数来自动生成。
什么是 Generator
(生成器)?
基本概念
Generator
函数(生成器)是ES6
提供的一种异步编程解决方案,语法行为与传统函数完全不同。
- 语法上,可以把它理解成一个状态机,封装了多个内部状态
- 形式上,
Generator
函数是一个普通函数,但是有两个特征:
①function
关键字与函数名之间有一个星号(*
)
② 函数体内部使用yield
表达式,yield
(翻译:产出)用来定义不同的内部状态
执行 Generator
函数,并不会像普通函数一样执行内部代码最终返回一个结果值,而是会返回一个遍历器(Iterator
)对象。下面是一个 Generator
函数:
function* helloWorldGenerator() {
yield 'hello'; // 状态 hello
yield 'world'; // 状态 world
return 'ending'; // 最终状态return定义 ending
}
var hw = helloWorldGenerator(); // 执行,返回遍历器对象 hw
复制代码
执行生成器函数返回的遍历器对象,我们通过它自身的 next
方法来启动,以后每次调用next
方法依次遍历,每次遍历会返回一个有着value
和done
两个属性的对象。
hw.next(); // { value: 'hello', done: false }
hw.next(); // { value: 'world', done: false }
hw.next(); // { value: 'ending', done: true }
hw.next(); // { value: undefined, done: true }
复制代码
value
属性表示当前的内部状态的值,是yield
表达式后面那个表达式的值;done
属性是一个布尔值,表示是否遍历结束。
yield
表达式
yield
表达式就是暂停标志。即调用next
方法遇到yield
表达式,就暂停执行后面的操作,并将紧跟在yield
后面的那个表达式的值,作为返回的对象的value
属性值。
注意:
return
语句作为程序的终止,也就是会作为遍历器对象遍历结束的标志。遍历遇到return
语句,会将它后面的表达式的值作为返回的对象的value
属性值,如果遍历结束没有return
语句,则返回对象的value
属性值为undefined
(程序结束默认就是return undefined;
)yield
表达式只能用在Generator
函数里面yield
表达式如果用在另一个表达式之中,必须放在圆括号里面yield
表达式用作函数参数或放在赋值表达式的右边,可以不加括号
警告: 不要在 forEach、map、filter...
等方法内,将回调函数声明为生成器函数,然后再使用yield
表达式,因为它们规定了接收参数就是一个普通函数,不会接收生成器函数,最好的做法就是将外部函数声明为生成器函数,然后使用for
循环(除了这里的原因外,大家也不要抵制写for
循环,虽然它麻烦,但是它的效率却是最高的)
Symbol.iterator
与 for...of
循环
ES6
规定,默认的Iterator
接口部署在数据结构的Symbol.iterator
属性,或者说,一个数据结构只要具有Symbol.iterator
属性,就可以认为是“可遍历的”(iterable
)。
因为 Generator
函数就是遍历器生成函数,所以可以把 Generator
赋值给对象的Symbol.iterator
属性,从而使得该对象具有 Iterator
接口。
默认调用 Iterator
接口(即Symbol.iterator
方法)的场合:
- 解构赋值
- 扩展运算符
yield*
表达式- 任何接受数组作为参数的场合(
Array.from()、Promise.all()、Promise.race()...
)
for...of
循环内部调用的是数据结构的Symbol.iterator
方法。
for...of
循环可以使用的范围:
- 数组(
Array
) Set
和Map
结构- 类数组对象(
arguments
对象、DOM NodeList
对象) Generator
函数返回的遍历器对象- 字符串(
String
)
next
方法
yield
表达式本身没有返回值,或者说总是返回undefined
。next
方法可以带一个参数,该参数就会被当作上一个yield
表达式的返回值。
function *foo(x) {
var y = x * (yield "Hello"); // yield "Hello" 即yield表达式
return y;
}
var it = foo( 6 );
var res = it.next(); // 启动生成器,不传入参数
res.value; // "Hello"
res = it.next( 7 ); // 向等待的yield传入7,即设置上一个yield表达式结果为7
res.value; // 42
复制代码
注意: next
方法携带的参数,是上一个yield
表达式的返回值。另外,第一次调用next
方法,代表启动生成器,还没有暂停的 yield
来接受这样一个值,所以即使你写了也是无效,规范和所有兼 容浏览器都会默默丢弃传递给第一个 next()
的任何东西。
return
方法
遍历器对象的return
方法,可以返回给定的值,并且终结遍历 Generator
函数。若不提供参数,则返回值的value
属性为undefined
。 如果for...of
循环提前退出(通常是因为出错,或者有break
语句),就会调用return
方法。
function* gen() {
yield 1;
yield 2;
yield 3;
}
var g = gen(); // 得到遍历器对象
g.next() // { value: 1, done: false }
g.return('foo') // { value: "foo", done: true } 返回指定值并终结遍历
g.next() // { value: undefined, done: true }
复制代码
注意: 如果 Generator
函数内部有try...finally
代码块,且正在执行try
代码块,那么return
方法会导致立刻进入finally
代码块,执行完以后,整个函数才会结束。
throw
方法
遍历器对象的throw
方法,用于在函数体外抛出错误,然后在 Generator
函数体内捕获。
注意:
- 如果
Generator
函数体内未捕获(try...catch
),那么可以在外部捕获,如果外部也没有捕获,那么程序将报错,直接中断执行 throw
方法抛出的错误要被内部捕获,前提是必须至少执行过一次next
方法throw
方法被内部捕获以后,会附带执行一次next
方法Generator
函数体内抛出的错误,也可以被函数体外的catch
捕获Generator
函数体内抛出错误且没有被内部捕获,它就不会再执行下去了
next()、throw()、return()
的共同点
都是让 Generator
函数恢复执行,只是使用了不同的语句替换yield
表达式:
next()
是将yield
表达式替换成一个值throw()
是将yield
表达式替换成一个throw
语句return()
是将yield
表达式替换成一个return
语句
生成器委托—— yield*
表达式
yield*
表达式用于在 Generator
函数内部,调用另一个 Generator
函数,省去了我们手动完成遍历的繁琐过程。
function* foo() {
yield 'a';
yield 'b';
}
function* bar() {
yield 'x';
yield* foo();
yield 'y';
}
// 等同于
function* bar() {
yield 'x';
yield 'a';
yield 'b';
yield 'y';
}
复制代码
生成器 + Promise
—— async/await
一个简单的 🌰 :
function foo(x,y) {
return request( // request 是一个用 Promise 封装的异步请求方法
"http://some.url.1/?x=" + x + "&y=" + y
);
}
function *main() {
try {
var text = yield foo( 11, 31 );
console.log( text );
} catch (err) {
console.error( err );
}
}
var it = main();
var p = it.next().value;
// 等待promise p决议
p.then(
function(text){
it.next( text );
},
function(err){
it.throw( err );
}
)
复制代码
考虑如果该生成器函数内有多个异步请求,那么每次我们都需要如此处理,即等待promise
决议后手动调用next
方法完成生成器函数,那代码会越来越多,显得繁琐和难于处理,所以我们需要一个工具函数来简化我们的操作。下面是一个工具函数的 🌰 :
function run(gen) {
var args = [].slice.call( arguments, 1), it;
// 在当前上下文中初始化生成器
it = gen.apply( this, args );
// 返回一个promise用于生成器完成
return Promise.resolve()
.then( function handleNext(value){
// 对下一个yield出的值运行
var next = it.next( value );
return (function handleResult(next){
// 生成器运行完毕了吗?
if (next.done) {
return next.value;
}
// 否则继续运行
else {
return Promise.resolve( next.value )
.then(
// 成功就恢复异步循环,把决议的值发回生成器
handleNext,
// 如果value是被拒绝的 promise,
// 就把错误传回生成器进行出错处理
function handleErr(err) {
return Promise.resolve(
it.throw( err )
)
.then( handleResult );
}
);
}
})(next);
} );
}
// 使用
function *main() {
// ..
}
run( main );
复制代码
这个工具函数也不必死磕,注意到它的返回值是Promise
且内部是通过递归完成生成器函数的执行就好了。
其实上面这就是async/await
的实现原理,对于async/await
需要注意的地方:
async
函数的返回值是Promise
对象,可以用then
方法指定下一步的操作await
命令后面是一个Promise
对象,返回该对象的结果。如果不是Promise
对象,就直接返回对应的值。await
命令后面是一个thenable
对象,那么await
会将其等同于Promise
对象- 任何一个
await
语句后面的Promise
对象变为reject
状态,那么整个async
函数都会中断执行
有时,我们希望即使前一个异步操作失败,也不要中断后面的异步操作。两种解决办法:
- 将
await
放在try...catch
结构里面 await
后面的Promise
对象再跟一个catch
方法
注意: 如果不是特别强调执行顺序,那么请让多个请求并发执行,使用 Promise.all
方法,然后再 await
等待所有异步的执行结果,利用并发来提高程序性能。
作者:伟大的兔神
链接:https://juejin.im/post/5ef5681d6fb9a07e847229ff