赞
踩
Ta生成的就是一个 Iterator对象 。
function *gen() {
yield 1;
yield 2;
return 3;
}
const it = gen();
console.log(it.next()); // { value: 1, done: false }
console.log(it.next()); // { value: 2, done: false }
console.log(it.next()); // { value: 3, done: false }
console.log(it.next()); // { value: undefined, done: true }
console.log(it.next()); // { value: undefined, done: true }
普通函数的执行会形成一个调用栈,入栈和出栈是一口气完成的。而Generator必须得手动调用next()才能往下执行,相当于把执行的控制权从引擎交给了开发者。
所以Generator解决的是流程控制的问题。
它可以在执行过程暂时中断,先执行别的程序,但是它的执行上下文并没有销毁,仍然可以在需要的时候切换回来,继续往下执行。
最重要的优势在于,它看起来是同步的语法,但是却可以异步执行。
对于一个Generator函数来说,什么时候该暂停呢?就是在碰到yield关键字的时候。
function *gen() {
console.log('a');
yield 13 * 15;
console.log('b');
yield 15 - 13;
console.log('c');
return 3;
}
const it = gen();
运行结果:
看上面的例子,第一次调用it.next()
的时候,碰到了第一个yield
关键字,然后开始计算yield
后面表达式的值,然后这个值就成了it.next()
返回值中value
的值,然后停在这。这一步会打印a,但不会打印b。
以此类推。return的值
作为最后一个状态传递出去,然后返回值的done
属性就变成true
,一旦它变成true
,之后继续执行的返回值都是没有意义的。
这里面有一个状态传递的过程。yield把它暂停之前获得的状态传递给执行器。
执行器传递状态给状态机内部
这有什么用?如果能在执行过程中给状态机传值,我们就可以改变状态机的执行条件。你可以发现,Generator
是可以实现值的双向传递
的。
为什么要作为上一个yield
的返回值?你想啊,作为上一个yield
的返回值,才能改变当前代码的执行条件,这样才有价值不是嘛。这地方有点绕,仔细想一想。
好吧,既然引擎把Generator的控制权交给了开发者,那我们就要探索出一种方法,让Generator的遍历器对象可以自动执行。
function* gen() { yield 1; yield 2; return 3; } function run(gen) { const it = gen(); let state = { done: false }; while (!state.done) { state = it.next(); console.log(state); } } run(gen);
但想想我们是来干什么的,我们是来探讨JavaScript异步的呀。这个简陋的run函数能够执行异步操作吗?
function fetchByName(name) { const url = `https://api.github.com/users/${name}/repos`; fetch(url).then(res => res.json()).then(res => console.log(res)); } function *gen() { yield fetchByName('veedrin'); yield fetchByName('tj'); } function run(gen) { const it = gen(); let state = { done: false }; while (!state.done) { state = it.next(); } } run(gen);
事实证明,Generator会把fetchByName当做一个同步函数来执行,没等请求触发回调,它已经将指针指向了下一个yield。我们的目的是让上一个异步任务完成以后才开始下一个异步任务,显然这种方式做不到。
我们已经让Generator自动化了,但是在面对异步任务的时候,交还控制权的时机依然不对。
Generator异步自动执行 ---- (在回调中交还控制权)
哪个时间点表明某个异步任务已经完成?当然是在回调中咯。
我们来拆解一下思路。
然后yield asyncTask()的返回值得是一个函数,它接受异步任务的回调作为参数。因为Generator只有yield的返回值是暴露在外面的,方便我们控制。
最后在回调中移动指针。
function thunkify(fn) {
return (...args) => {
return (done) => {
args.push(done);
fn(...args);
}
}
}
这就是把异步任务的其他参数和回调参数拆分开来的法宝。是不是很简单?它通过两层闭包将原过程变成三次函数调用,第一次传入原函数,第二次传入回调之前的参数,第三次传入回调,并在最里一层闭包中又把参数整合起来传入原函数。
是的,这就是大名鼎鼎的thunkify。
const fs = require('fs'); const thunkify = require('./thunkify'); const readFileThunk = thunkify(fs.readFile); function *gen() { const valueA = yield readFileThunk('/Users/veedrin/a.md'); console.log('a.md 的内容是:\n', valueA.toString()); const valueB = yield readFileThunk('/Users/veedrin/b.md'); console.log('b.md 的内容是:\n', valueB.toString()); } function run(gen) { const it = gen(); function next(err, data) { const state = it.next(data); if (state.done) return; state.value(next); } next(); } run(gen);
我们完全可以把回调函数抽象出来,每移动一次指针就递归一次,然后在回调函数内部加一个停止递归的逻辑,一个通用版的run函数就写好啦。上例中的next()其实就是callback()呢。
co是一个真正的异步解决方案,因为它暴露的接口足够简单
import co from './co';
function fetchByName(name) {
const url = `https://api.github.com/users/${name}/repos`;
return fetch(url).then(res => res.json());
}
function *gen() {
const value1 = yield fetchByName('veedrin');
console.log(value1);
const value2 = yield fetchByName('tj');
console.log(value2);
}
co(gen);
也许是终极异步解决方案
我知道你想说什么,写一个异步调用还得引入一个npm包(虽然是大神TJ写的包)
妈卖批的npm!
当然是不存在的。如果一个特性足够重要,社区的呼声足够高,它就一定会被纳入标准的。马上我们要介绍的就是血统纯正的异步编程家族终极继承人——爱新觉罗·async。
封装原理:
import co from 'co';
function fetchByName(name) {
const url = `https://api.github.com/users/${name}/repos`;
return fetch(url).then(res => res.json());
}
co(function *gen() {
const value1 = yield fetchByName('veedrin');
console.log(value1);
const value2 = yield fetchByName('tj');
console.log(value2);
});
使用:
function fetchByName(name) {
const url = `https://api.github.com/users/${name}/repos`;
return fetch(url).then(res => res.json());
}
async function fetchData() {
const value1 = await fetchByName('veedrin');
console.log(value1);
const value2 = await fetchByName('tj');
console.log(value2);
}
fetchData();
看看这无缝升级的体验,啧啧
别被新的关键字吓到了,它其实非常灵活。
async function noop() {
console.log('Easy, nothing happened.');
}
复制代码这家伙能执行吗?当然能,老伙计还是你的老伙计。
async function noop() {
const msg = await 'Easy, nothing happened.';
console.log(msg);
}
复制代码同样别慌,还是预期的表现。
只有当await关键字后面是一个Promise的时候,它才会显现它异步控制的威力,其余时候人畜无害。
function fetchByName(name) {
const url = `https://api.github.com/users/${name}/repos`;
return fetch(url).then(res => res.json());
}
async function fetchData() {
const name = await 'veedrin';
const repos = await fetchByName(name);
console.log(repos);
}
虽然说await
关键字后面跟Promise
或者非Promise
都可以处理,但对它们的处理方式是不一样的。非Promise
表达式直接返回它的值就是了,而Promise表达式则会等待它的状态从pending
变为fulfilled
,然后返回resolve
的参数。它隐式的做了一下处理。
注意看,fetchByName('veedrin')
按道理返回的是一个Promise实例,但是我们得到的repos
值却是一个数组,这里就是await关键字隐式处理的地方。
我们把它弄到一个作用域里去
import sleep from './sleep';
function work() {
[1, 2, 3].forEach(async v => {
const rest = await sleep(3);
console.log(rest);
});
return '睡醒了';
}
work();
不好意思,return '睡醒了’没等异步操作完就执行了,这应该也不是你要的效果吧。
所以这种情况,只能用for循环来代替,async和await就能长相厮守了。
import sleep from './sleep';
async function work() {
const things = [1, 2, 3];
for (let thing of things) {
const rest = await sleep(3);
console.log(rest);
}
return '睡醒了';
}
work();
async可不止一颗糖哦。它是Generator、co、Promise三者的封装。如果说Generator只是一个状态机的话,那async天生就是为异步而生的。
import sleep from './sleep';
async function work() {
const needRest = await sleep(6);
const anotherRest = await sleep(3);
console.log(needRest);
console.log(anotherRest);
return '睡醒了';
}
work().then(res => console.log('?', res), res => console.error('?', res));
因为async函数返回一个Promise实例,那它本身return的值跑哪去了呢?它成了返回的Promise实例resolve时传递的参数。也就是说return '睡醒了’在内部会转成resolve(‘睡醒了’)。
我可以保证,返回的是一个真正的Promise实例,所以其他特性向Promise看齐就好了。
也许你发现了,上一节的例子大概要等9秒多才能最终结束执行。可是两个sleep之间并没有依赖关系,你跟我说说我凭什么要等9秒多?
之前跟老子说要异步流程控制是不是!现在又跟老子说要并发是不是!
我…满足你。
方法一:
import sleep from './sleep';
async function work() {
const needRest = await Promise.all([sleep(6), sleep(3)]);
console.log(needRest);
return '睡醒了';
}
work().then(res => console.log('?', res), res => console.error('?', res));
import sleep from './sleep';
async function work() {
const onePromise = sleep(6);
const anotherPromise = sleep(3);
const needRest = await onePromise;
const anotherRest = await anotherPromise;
console.log(needRest);
console.log(anotherRest);
return '睡醒了';
}
work().then(res => console.log('?', res), res => console.error('?', res));
办法也是有的,还不止一种。手段都差不多,就是把await往后挪,这样既能搂的住,又能实现并发。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。