From Iterator To Async

作者 Wenhao Liao 日期 2017-03-24
JS
From Iterator To Async

代码是人脑逻辑的抽象体现,我们始终在真实发生的反直觉的逻辑与人脑逻辑的平衡中挣扎。 我们的顶层认知思维是单线程且倾向于顺序性的,这使得我们当遇到一些不得不打乱这种顺序和单任务执行的抽象逻辑时变的很迷惑,其中最”臭名昭著”的便是异步编程,它的内部机制和原始的代码抽象是非常反直觉的存在,让我们的大脑在一次次无可预期的报错中轰鸣。 作为编程语言的创造者,我们不断的在优化对于这些“魔性”过程的抽象代码,使得其更加类似于我们的思维过程,让它变得越来越有序,并具稳定性,可控性。 这一切的开始,就好似每一个新生的我们接触到数学时必须反复重复诵读的数字表一样: 1, 2, 3, 4, 5….

秩序之源:顺序

在所有语言里不可或缺的就是对于数据堆中的元数据的一一获取与处理,循环的方式是最粗暴简单有效的方式,然而追求优雅的我们不满足于这种看起来略显“粗糙”的表达方法,封装了更优雅的遍历API,在js中,诸如Array.prototype系列的forEachmapreduce,到Object系列的for..in,让我们的代码更具简洁达意。

Loop & traverse

var arr = [1,2,3,4,5,6]
len = arr.length,
i;
for(i = 0; i<len; i++) {
//do sth with arr[i]
}
//Everything seems better;
//抽象出这个处理的逻辑,以备复用
var doubleIt = (num, idx, arr) => {
arr[idx] = num*2;
}
arr.forEach(doubleIt); //Looks better.
//我们总是倾向于在具体执行一些重复的逻辑时用最短小精悍的语句
console.log(arr); //[2,4,6,8,10,12]

难以抑制的控制欲

然而对于一个过程的掌控不仅限于让其顺畅的运作起来,我们更加渴望的是控制遍历这个过程起停,我们可以再任意时刻观察到遍历的位置,并且让它停下来,并且在未来的某时再让其继续,如此反复直到遍历完毕。听起来好像是我们的控制欲一发不可收拾,而实际上这是实际问题对我们的警示————掌握控制权总是更好的选择,但有时也许我们应该把这些控制权隐藏起来(封装,以及后面会出现的控制信任问题)。 遍历的出现让我们预见了这个能力。

Iterator

首先我们来构造一个鸭子类型遍历器:

function fakeIterator (array) {
var nextIdx = 0;
return {
next: function() {
return nextIdx < array.length ? [value: array[nextIndex++], done: false] : [value: undefined, done: true]
}
}
}
var it = fakeIterator([1, 2, 3, 4, 5, 6]);
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: 4, done: false }
console.log( it.next() ); //{ value: 5, done: false }
console.log( it.next() ); //{ value: 6, done: false }
console.log( it.next() ); //{ value: undefined, done: true }

我们可以注意到我们的鸭子遍历器传入一个参数是数组,这相当于为这个遍历器提供了数据结构,而这个鸭子遍历器为我们提供了访问其值的方式。 在ES6中,提供了iterator接口,它为所有数据结构提供统一的访问机制,当我们使用ES6的另一个新特性for...of时,会自动寻找iterator接口, 这个接口部署在数据结构的Symbol.ietrator属性,它是作为一个iterable的特征。当我们调用Symbol.iterator这个方法时,就会返回一个遍历器生成函数。

数组,一些类数组,set,map这四种数据结构都具有iterator接口。

var arr2 = [1,2,3,4];
var it2 = arr2[Symbol.iterator]();
console.log( it2.next() ); //{done:false, value:1}
for (let num of arr2) {
// 默认自带接口 等价于 for(let num of it2)
console.log(num);
}// 1,2,3,4

对于类数组对象,我们可以给其Symbol.iterator方法引用数组的iterator接口:

var itObj = {
0: 'a',
1: 'b',
2: 'c',
length: 3,
[Symbol.iterator]: Array.prototype[Symbol.iterator] // or [][Symbol.iterator]
};
for (let item of itObj) {
console.log(item);
} // 'a', 'b', 'c'

如果对象是一个普通对象,那我们引用数组的iterator接口并不会生效(将上面对象的key改成非zero-based的字符串则这个对象将不再是类数组对象),解决办法是我们必须手动定义Symbol.iterator方法,就像之前我们定义鸭子类型遍历器时一样。

var itObj2 = {
data: [1,2,3,4,5],
[Symbol.iterator]() {
const self = this;
let index = 0;
return {
next() { //等同于 next: function()
if ( index < self.data.length ) {
return {
value: self.data[index++],
done: false
};
} else {
return {value: undefined, done: true}
}
}
};
}
}
for (let item of itObj2) {
console.log(item); //1,2,3,4,5
}
var iter2 = itObj2[Symbol.iterator]();
console.log(iter2.next()); //{done: false, value:1}

How wonderful!!

这种push => out的机制一直是我们想要在程序中保持的,乍一看有点点像惰性求值thunk? 这些概念从脑子里闪过, 它们在编程史上的出现使得程序在运行期有了更好的‘颗粒感’,随之而来的就是更好的控制,可这一路上我们为之挣扎了很久…

深渊

我们恐惧一切无法预料的东西带来的不利,这让我们疯狂的想要尽可能控制它到来时造成的混乱。在远久的时代,我们需要一种机制,一种类似于延时触发的机制,setTimeout出现了:

var timer = setTimeout(function(){
console.log('hi there i am back');
},1000);
//do sth... 'maybe' we need 1000ms to finish it.
// 1000ms after, 'hi there i am back'

但是这只是基于时间量程的,且由于延迟执行的机制让我们也无法保证在1秒后event quene已经清空,所以这样的方式并不可靠,good luck.

许多过程我们并无法用时间给予统一的度量标准,我们想要一种机制,让其在做完任意事件后触发指定的事件,这也就是callback, 这种机制很有效的解决了时间的不确定性,在单步操作中非常有效。

它的机制是我们向一个会耗费一些时间的操作里传入回调函数,也就是我们希望它在运行完毕时执行的函数:

//用setTimeout模拟一个异步事件
function request(url, callback) {
var filePool = {
'file1' : 'file1 load successfully',
'file2' : 'file2 load successfully'
};
var randomDelay = Math.round(Math.random() *2000) + 1000;
setTimeout(function(){
callback(filePool[url]);
}, randomDelay);
}
request('file2', function(res){
console.log('Callback function executed');
console.log(res);
});
//after random second..
//Callback function executed
//file2 load successfully

Callbackhell

由以上的形式,我们如果需要将多个异步事件串联起来,那么将会是这样的形式:

request('file1', function(res1){
console.log(res1);
request('file2', function(res2){
console.log(res2);
request('file3', function(res2){
console.log(res3);
});
});
});

OPPS! 这样看起来似乎不太雅观。诶等等,哟,这不就是大名鼎鼎的callback hell吗! 我们必须意识到,回调地狱的深渊并不是缩进(indent)所造成的丑陋的代码这么简单,更深层次的限制在于:

这种一环死扣一环的触发机制带给我们的是一种牢不可破的控制流程。我们给自己亲手戴上了脚镣和手铐。

我们无法自由的控制它的进程,无法跳过某一个过程,我们只能尽可能的手动的设置一些非常specific的代码去阻挠它的脚步(比如在某个回调中插入破坏力巨大的return, 或者if(你丑){你走开}else{请进} 这种丑陋的条件语句等)。

于是,我们的控制欲再次爆发。

So just get out of my way you ugly and stubborn xxx!!!

(我还不知道该拿什么词形容它?顽固的XX??)

Thunk

这个名词诞生于60年代,这是一个关于“求值策略”的争论,我们到底是应该传入值的时候就立刻得到它,还是我们先将我们要的结果保存起来,当我们真正需要它的时候,索取。 (敲黑板,惰性的概念深入人心啊!)

它大概是这样的:

//我们有一个加法函数
var add = function(){
return [].slice.call(arguments).reduce((a,b)=>a+b);
}
//thunk style
var thunk = function() {
return add(1,2,3);
}
console.log(thunk()); //6

嗯,有人可能会问:这有什么卵用 = = ? 其实这个问题让我回来我也不能很好的说清楚,有心得的同学可以指点指点我。 但是当问题到了异步范畴的时候,为了将一个异步请求+注入回调的过程抽象成一个更简洁的形式,它展现出了威力。

我们先来伪装一个AJAX请求:

//第一个参数是请求地址,第二个是回调函数
function fakeAjax(url, cb) {
var fake_responses = {
"file1": "The first text",
"file2": "The middle text",
"file3": "The last text"
};
// 伪装随机请求时间 (就是这么粗暴..)
var randomDelay = Math.round(Math.random() * 2000) + 1000;
render('Requesting: ' + url + ', and this will take ' + randomDelay + 'ms', 'requesting');
render("Requesting: " + url, 'requesting');
// 伪装请求完毕执行回调函数
setTimeout(function () {
cb(fake_responses[url]);
}, randomDelay);
}
//为了更友好的可视化,我们将console替换为一个render函数
function render(text, className) {
document.body.innerHTML += '<p class="' + className + '">' + text + '</p>';
}

然后我们提一个需求,那就是我们想按顺序请求file1,file2,file3, 一旦它们请求完毕,立即打印,但是要按顺序。

整理一下思路,也就是说,这是一个遍历的过程,一旦遍历到没有请求返回的时候就break,然后就是不断的遍历。 遍历的触发点就是有任意一个请求返回。

有点感觉了吧? 也就是说我们需要编写一个处理顺序&打印消息的回调函数。

//传入需要请求的文件地址组成的数组。
function watchAndHandle(fileList) {
//用来存储请求信息
var oResPool = {};
var aFileNames = fileList;
//这个函数返回一个函数作为文件请求的回调
//第二个参数为请求完毕执行回调时传入的response
return function (filename, contents) {
//如果对应文件的value被设为0,则代表请求已经完毕
if (!(oResPool[filename] in oResPool)) {
render('Finish: ' + filename + ' is ready!', 'ready');
oResPool[filename] = contents;
}
//遍历
for (var i = 0; i < aFileNames.length; i++) {
if (aFileNames[i] in oResPool) {
if (typeof oResPool[aFileNames[i]] === 'string') {
render('Output: ' + oResPool[aFileNames[i]], 'mes');
oResPool[aFileNames[i]] = false;
}
} else {
//如果对应文件没有请求完毕,结束循环。
render('Block: But ' + aFileNames[i] + ' is not ready yet.. continue waiting...',
'not-ready')
return;
}
}
// 遍历成功,所有文件都成功请求了,打印请求全部成功
render('Complete :)!!', 'suc');
}
}

现在我们还有一件事没有处理,那就是我们压根还没使用thunk啊 = = ,没错,下面我们来看看用thunk能做些什么。

// Thunk style
function requestFile(fileURL) {
fakeAjax(fileURL, function (text) {
//调用上面的回调函数
handleResponse(fileURL, text);
});
}

我们可以使用这个thunk函数将对应文件请求的过程保存下来,然后,重点来了:

var handleResponse = watchAndHandle(["file1", "file2", "file3"]);
requestFile('file1');
requestFile('file2');
requestFile('file3');

效果:

OMG, WHAT I JUST DID!

我们用三行synchronous的代码完成了一个带流程控制的三文件异步请求!?

现在我们可以看到thunk的威力,它封装了请求后的回调等一切复杂的逻辑(maybe),我们只需要传入需要请求的文件地址即可。 尽管从技术的角度来看它也许只是一个装饰者, 但这不正是我们需要的吗? 让我们的代码看起来更符合我们的思维流程。

More thunk

不过瘾,既然thunk可以封装回调处理的逻辑,那不如我们再看看其他的?

我们继续改造getFile函数:

/*
Create a active thunk ( immediately call )
Using closure to maintain state
These is only two scenarios here:
1. fakeAjax call callBack first. ---- we store the data sent back and wait runing with it.
2. thunk call callBack first. ---- we store the cb and wait ajax to call it.
*/
function getFile(file) {
var text, fn;
fakeAjax(file, function __responseHandler__(responseMes) {
if (fn) fn(responseMes);
else text = responseMes;
});
return function __callBackHandler__(cb) {
if (text) cb(text);
else fn = cb;
}
}

HERE’S THE MAGIC!

我们来看看上面做了什么:

  • 我们创造了一个立即调用异步请求的thunk(active thunk)函数。
  • 我们使用闭包来保存AJAX的请求状态,还有回调函数的传入状态。
  • 如果AJAX请求先完毕,但回调函数还未传入,那我们缓存它传回的信息。
  • 如果回调函数传入了,但AJAX还未请求完毕,那我们缓存这个回调函数。

然后,一切顺理成章的融合在一起,形成了完美的互补,无论谁先被缓存,最后我们都会顺利的调用回调函数并且传入回调信息!

在看到这种方法之前,我从来没意识到还可以这样做,闭包果然是神器。

但是在进行实际的异步请求的时候它的问题又暴露了:

var thunk1 = getFile('file1');
var thunk2 = getFile('file2');
var thunk3 = getFile('file3');
// What a powerful mechanism! Your code turn outs nice!
// But still stack in callback hell.
thunk1(function (responseOfFileOne) {
render('Output: ' + responseOfFileOne, 'mes');
thunk2(function (responseOfFileTwo) {
render('Output: ' + responseOfFileTwo, 'mes');
thunk3(function (responseOfFileThree) {
render('Output: ' + responseOfFileThree, 'mes');
render('Complete :)!!', 'suc');
});
});
});

面目可憎的回调地狱!

但是这种尝试让我们更好的明白了thunk的意义: 函数形式的转化,对于状态的持有。

该死的异步,我们始终还是没能前进,直到….

击碎异步的桎梏

在现实中,有一个场景大家很熟悉: 上课的时候,老师突然拿出一张表,说:从第一个开始,签名,然后传给下一个人。

等等,这不就是异步吗? 每个人签名的时间是未知的,我们不知道多长时间后这张表会传给下一个人,而且还有可能出现别的情况比如下课了,老师说”别填了,下次继续“。

而这就是我们苦苦希望在code里实现的,在我们的脑海里,呈现出的应该是:

第一位同学填表 其他同学等待
第一位同学填表完毕
第二位同学填表 其他同学等待
第二位同学填表完毕
第三位同学填表 其他同学等待

而我们的代码是:

fillUpForm( A, tellNextOnetoContinue(){
fillUpForm( B, tellNextOnetoContinue() {
fillUpForm( C, tellNextOnetoContinue() {
//......
})
})
})

就形式而言,前者是分离的代码块,就像老师布置的任务的具体化,而后者是一体的函数, 我们始终追求的解耦在后者面前简直是无稽之谈,为什么我们就是不能让我们的执行过程看起来跟老师的任务外形上更一致呢? 于是,终于…. 我们等来了承诺

Promise

什么是承诺? 简单来说: 就是一个同学告诉老师,我填完了表就会传给下一个同学的,如果我填不完我也会告诉你为什么!

具体的API细节请参考:

它的形式大概是这样的:

  • 创建一个Promise实例
  • 指定resolve处理函数与reject函数
  • 或者在事情做完的时候调用resolve(传入回调信息) or reject(传入的错误/拒绝信息)
  • 使用.then指定第二步的内容

Show me the code:

我们现在来模拟一下这个填表的过程!jsbin

var taskState = [];
var studentId = 0;
function doSthAsync() {
return new Promise(function (resolve, reject) {
//模拟填表的过程
var randomDelay = Math.round(Math.random()*1000) + 1000;
console.log(`学生${++studentId}正在填表啦!大概耗时${randomDelay/1000}秒`);
setTimeout(function __fillUpForm__() {
resolve(`学生${studentId}:老师我填完啦!我现在交给下一个同学咯。`);
}, randomDelay);
});
}
// Promise.resolve()可以返回一个promise对象并立即调用resolve并传入传入的参数。如果不传任何参数,可以充当一个promise chain的初始化。
Promise.resolve('第一组的同学,大家依次填表哦,填完就传给下一个同学。')
.then(function (res) {
console.log(res);
return doSthAsync();
})
.then(function (res1) {
console.log(res1);
return doSthAsync();
})
.then(function (res2) {
console.log(res2);
return doSthAsync();
})
.then(function (res3) {
console.log(res3);
return doSthAsync();
})
.then(function (res4) {
console.log(res4);
return doSthAsync();
})
.then(function (res5) {
console.log(res5);
console.log('我们这一组填完啦!')
})

效果:

现在我们来回到之前我们的,异步请求文件的demo,我们现在可以将它改装一下了:

首先我们把getFile这个thunk-style修改一下,让它返回一个promise。

function getFile(file) {
// what do we do here?
return new Promise(function (resolve, reject) {
fakeAjax(file, function (response) {
resolve(response);
});
});
}

接着是fakeAJAX:

function fakeAjax(url, cb) {
var fake_responses = {
"file1": "The first text",
"file2": "The middle text",
"file3": "The last text"
};
var randomDelay = Math.round(Math.random() * 2000) + 1000;
render('Requesting: ' + url + ', and this will take ' + randomDelay + 'ms', 'requesting');
render("Requesting: " + url, 'requesting');
setTimeout(function () {
cb(fake_responses[url]);
}, randomDelay);
}

然后我们来进行任务布置:

//回顾一下上面的填表的过程,我们在每一步同时做了两件事,我们应该更多的遵循一次做一件事的原则,于是请求文件我们拆分为了:
// 1. 请求文件
// 2. 渲染请求结果
// 3. 请求下一个文件
getFile('file1')
.then(function (response1) {
render(response1, 'mes')
})
.then(function () {
return getFile('file2');
})
.then(function (response2) {
render(response2, 'mes');
})
.then(function () {
return getFile('file3');
})
.then(function (response3) {
render(response3, 'mes');
})
.then(function () {
render("Complete!", 'suc');
});

效果:

至此我们已经取得了卓越的进步,还记得之前的脑海里的任务表吗? 我们完全是按照那张表来书写代码的!

天啊,我们的大脑跟代码同步了!

要知道,当我们的大脑逻辑与代码逻辑编写出现冲突的时候,是最容易诞生bug的。

我们这样做不仅使我们像同步(synchronous)那样书写代码,同时背后控制住了异步(asynchronous)的逻辑!还有效避免了我们的思维发生混乱,还有…

恩恩,不能高兴的太早,我们再好好看看上面的code,似乎有点?重复? 没错! 为了遵循DRY原则,我们得想办法让它变得不这么重复,then…then…then…听起来让人烦躁。

// More abstract & flexible style
['file1', 'file2', 'file3']
.map(getFile)
.reduce(function (chain, filePromise) {
return chain.then(function () {
return filePromise;
}).then(function (res) {
render(res, 'mes');
})
}, Promise.resolve())
.then(function () {
render('Complete! :)', 'suc');
});

恩…没错,迭代方法大显神威,我们使用getFile进行请求初始化并返回一个promise实例,组成一个promise数组。接着,考虑到我们总是在链接两个任务,我们可以使用reduce但是别忘了我们必须传入一个初始值,还记得前面说的Promise.resolve()吗? 我们让它来给我们开个头,从而我们能获得一条Promise chain,拿到这条链条后,我们分开做两件事:

  • 1.返回这个promise实例 (第一次时这是Promise.resolve()返回的实例)
  • 2.渲染这个promise实例的返回信息

当所有的promise实例都resolved了,我们就渲染成功信息。

效果:

有一个细节的差异: 我们发现三个文件的请求准备信息都在头部打印了出来,这是由于使用map的时候我们几乎是同时初始化返回了Promise实例,在getFile内部我们调用了render函数,所以导致这样的结果。但返回结果的渲染依然是按顺序的。

对于Promise其实还有一些其他有用的api,比如Promise.all(),它接受一个数组,这个数组的元素都是promise实例,从而将它们一起包装为一个新的promise实例。而对于它的功能表现是: 当所有的promise实例都resolved时,才会进行下一部(用then链接起来)

我们把上面的布置任务再改改变成这样:

Promise.all(['file1', 'file2', 'file3'].map(getFile)).then(function (reps) {
reps.forEach(function (msg) {
render(msg, 'mes');
})
}).then(function () {
render('Complete! :)', 'suc');
});

效果:

很清楚的看见最后的返回信息一次性按顺序渲染到文档里。

哈哈哈哈哈,看看!从一开始到现在,我们的代码变的越来越简洁清晰。

回顾中,我们可以发现,当我们使用map,reduce进行promise chaining的时候,实际还做了一件事,那就是我们将上一个promise实例链接到下一个时的这个过程封装了,作为一段整体code,看起来外部我们并没有暴露任何这些promise中的其中一个的控制权,这一切都在内部进行了交接。

Perfect! 我再也不用担心我的异步调用被一些不可预料的因素剥夺控制权了。关于控制权,请参考跟我英文同名的kyle胖的书 You don’t know JS 中异步那一章: Promise Trust

洁癖和偏执的控制欲

回头看看,常看常新,反思我们现在所企及的高度,我们的代码真的就这么完美了吗?

也许??

又或者如果我的异步任务并不能总是这么走运的都是同一类型,能用统一的thunk函数进行map创造promise实例数组?

那岂不是我的代码就不得不出现成堆的then了…

没错,promise只是一个关于时间且thunk-style的wrapper,它只是改进了回调函数,提供了美好的API而已,不是吗?

看来我们如果需要更简洁的代码,也许我们得跨过.then.

Generator

Hey,还记得我们一开始提到的遍历器吗? 当我们执行遍历器函数时,返回的本质是一个遍历对象。

Generator函数在运行后返回的也是这样一个遍历对象,我们来看看它的特征:(废话了当然是function关键字与name之间有朵花 = = ! 其实是星号: *)

function* gen() {
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
}
var go = gen(); //???? 什么也没有发生。
console.log( typeof go); // object

所以generator函数执行过一次以后只是返回了一个Iterator对象而已,这个对象可以遍历函数内所有的yiled语句,函数每次都会在遇到yiled语句时停下来,直到我们使用next()函数让函数恢复执行并遇到下一个yiled时停下,以此反复,直到所有的yiled语句都已经被遍历过。

function* co() {
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
}
//既然本质都是遍历器对象,那么同样也可以使用for...of
for (let key of co()) {
console.log(key);
} //1, 2, 3, 4, 5
var c = co();
console.log(c.next()) // {done: true, value: 1}
console.log(c.next())
console.log(c.next())
console.log(c.next())
console.log(c.next())
console.log(c.next()) // 第六次是 {done: true, value: undefined}
//之后继续的话则总是done:true

具体细节请参考:

好了,我们这里不去讲太多语法细节,专注于我们的主线问题: 异步控制。

我们现在再来试着用generator函数来编写任务布置流程, 利用yield的”暂停“函数运行的效果,我们可以写出:

var gen = function* () {
var f1 = yield getFile('file1');
render(f1, 'mes');
var f2 = yield getFile('file2');
render(f2, 'mes');
var f3 = yield getFile('file3');
render(f3, 'mes');
}
var g = gen();

同样的同步写法,简单易懂,但是此刻我们要怎么让它一个个运行下去呢?

用gen.next()试试?

// Manual chaining
g.next().value.then(function(data){
g.next(data).value.then(function(data){
g.next(data).value.then(function(data){
g.next(data);
render('Complete! :)', 'suc')
})
})
});

我的天,如此粗暴的写法还真是可以诶….但这不又是….不行!不行!不行! (如果你没瞎,你知道我要什么)

回忆一下第一次我们遇到回调地狱时的做法是什么?

貌似是编写一个又臭又长的遍历promise状态的回调函数?

人家那其实是个callback&helper混合体….

啊对对对,逼格不能丢,对,我们需要一个helper,来让我们的generator函数自动执行, 当然执行的时机是上一个yiled后的异步请求成功时。

梳理下思路:

  • 遍历下去的条件是还有yield语句存在,那么就是 g.next.done !== true
  • 让我们的异步请求完成后调用g.next(),那么就是result.value.then里面调用next()
  • 每次遍历时我们得让value更新: g.next()赋值。
  • 考虑到我们的value实际上都是Promise实例,那我们还需要考虑resolve时的传参问题,给上面的next括号里都加上res参数

于是我们可以写出这样一个helper函数:

function run(gen) {
var g = gen;
function next(res) {
//result is a promise, in generator, the first next() will ignore the parameter passed in.
var result = g.next(res);
if (result.done) return result.value; //udefined
result.value.then(function (res) {
next(res);
});
}
next();
}

迫不及待了,赶紧测试一下效果,让我来输入这段芝麻开门密码: run(g)

很好,跟预期中一样,然后又是老套路,消灭重复!用遍历改写:

// Maybe Loop?
let gen = function* () {
for (let i = 0, task; task = ['file1', 'file2', 'file3'][i++];) {
let temp = yield getFile(task)
render(temp, 'mes');
}
};

可能有人会怒吼:装逼装到底,上迭代函数啊!

于是啪啪啪敲出了更短的:

let gen = function* () {
['file1', 'file2', 'file3'].forEach(function (task, idx) {
let temp = yield getFile(task);
render(temp, 'mes');
});
};

我只能说:

少年,你很有想法,可是… yield是不能出现在普通函数里的,forEach后面的函数不就是一个普通的…函数吗?

我们已经做的足够好,开心吗。

async

潮人们肯定都知道这是什么了,我最崩溃的是在于看到一篇外国友人的文章,像我一样细数异步编程的变迁,结果下面有位大哥评论:

You are SOOOO late dude, we’ve already switched to async…

好吧,我们都是变迁的弃儿…

别啰嗦了!快看看用async怎么写:

var asy = async function () {
var f1 = awiait getFile('file1');
render(f1, 'mes');
var f2 = awiait getFile('file2');
render(f2, 'mes');
var f3 = awiait getFile('file3');
render(f3, 'mes');
}

Ok, that’s all, we are good to go…

等等!执行器呢!helper呢!

先生你好,我们async函数里内置执行器的喔!

恩,这是一个强大的generator+promise写法的语法糖,可惜ES6还不支持啊。

可我们有babel哇!

More asynchronous

现在,我们已经走过了这么大一圈,见识了种种我们苦思冥想用来应对异步流程控制的方法,而接下来是运用这些方法,去做一些有趣的事情,比如…. 动画?

在下一篇文章里,我会总结我近期学习到的动画的实现方式,以及如何控制动画队列。

毕竟,咱们是前端,得做点正反馈短快平的事来刺激自己,不断地进步,总结。


所有用例代码可以在我的Github里找到: async

本文所有权…算了…随便转…知识共享…互相指导.

我的博客: kylewh

(妈呀真的跳票了好久不动啊…逃…)