javascript因为设计之初被设计成了单线程,So,这会致使一个问题,若是有一个任务的量过重很耗费时间,那这个任务后面的代码就会由于它被阻塞好久才能执行。javascript
有些man以为这段等待的时间蛮浪费的,因而冒出了一个想法,有木有办法不让咱们这么干等着,它操做它的,咱们剩下的程序继续执行本身的,当它最终拿到结果了再通知咱们。css
嗯……异步解决的就是这么个东东。。。java
但这样推理异步的诞生私觉得是错误的。git
事实上,javascript被设计出来的主要做用就是用来处理DOM的,它天生就是异步的,若是说有什么是为了适应这种设计而生,那么单线程这种设计才是故意而为之的。github
为何这么说呢?ajax
想象一下,咱们要一个元素在0.5秒的时间向左移动100px,接着再让它在0.5秒的时间往右移动100px,若是是多线程,这两个任务几乎会同时下单,也就意味着这个元素几乎不会动,这显然和咱们预期的结果不一样。编程
而若是是单线程若是是异步操做,那么这个元素会先向右运动,而后在0.5秒的时间完成运动后,将向左的运动做为宏任务
加入到callbacks queque
中,做为一轮单独在执行栈中再执行,纵然它又会被当作异步任务分发出去,但却确保了向左的操做是在向右操做完成之后的某个时机才开始执行的。json
虽说多线程不是不能作到(相似于锁这样的操做),但实现起来确定不如一个线程简单(一我的干事不存在多我的干事须要协调的问题),而javascript最初只用了10天的时间就被创造了出来!promise
在这个年代,咱们的网站再也不是一滩死水,咱们开始能经过异步的HTTP请求来更新咱们网页的部分信息,bash
咱们的代码中开始出现这样的书写结构
$.ajax({
type: "GET",
url: "地址!!",
data: {param1:xxx, param2:xxx},
dataType: "json",
success: function(data){
}
});
复制代码
上一段落咱们说过,异步任务帮咱们解决了阻塞问题,js的回调机制(事件环)帮咱们解决了异步任务的执行顺序问题,但成也萧何败萧何,有些场景咱们的异步任务是须要嵌套的,一层套一层,那么咱们的代码就会长成这样
$.ajax({
type: "GET",
url: "地址!!",
data: {param1:xxx, param2:xxx},
dataType: "json",
success: function(data){
$.ajax({
type: "GET",
url: "地址!!",
data: {param1:xxx, param2:xxx},
dataType: "json",
success: function(data){
$.ajax({
type: "GET",
url: "地址!!",
data: {param1:xxx, param2:xxx},
dataType: "json",
success: function(data){
}
});
}
});
}
});
复制代码
这就是所谓的回调地狱了
嗯....这维护起来同志们确定、铁定、必定呀!以为至关不方便! 因而就开始折腾。。。想去改变这种传统异步方法的书写形式,想办法让代码更易读易维护
Promise 的原理与用法详见个人这篇白菜大文 Promise深度学习—我のPromise/A+实现
这个年代,咱们在书写异步代码的形式上取得了必定程度的进步,咱们写起代码来是像这个样子滴
$('div').find().css()...
复制代码
嗯,开了个玩笑别介意。。。其实大致想法就是这样的,像jQ同样链式书写异步代码
read(url,encode){
return new Promise((resolve,reject)=>{
readFile(url,encode,(err,data)=>{
if(err)?reject(err):resolve(data);
})
})
}
read('1.txt','utf8').then(value=>{
return readFile(value,'utf8'); //根据1.txt的内容来查找读取2.txt
}).then(value=>{
return readFile(value,'utf8'); //根据2.txt的内容来查找读取3.txt
}).then((value)=>{
console.log(value); //输出3.txt的内容
}).catch((err)=>{
//deal with error
})
//下一次then接收的参数为上一次return的结果,若是这个return的结果为promise则为promise的结果
复制代码
嗯。。。好想好上很多?
emmm....好上很多才有鬼咧!
虽然经过promise的then方法让咱们实现了链式调用,但咱们还须要手动将本来的异步API进行一次封装,而且还要每次在then中将这个封装的函数return执行,这。。。。
[imortant] promise就像是一个异步API的包装器,它能将传统的异步API的
本体
和回调
部分进行分离,让咱们更好的专一于异步回调的处理。
我的以为单单是promise的话,其实至关的。。。鸡肋!真正使promise发扬光大的是在人们认识到不论怎样异步终究是异步终究是一种反人类的操做,咱们理应竖起大义的旗帜开始反击的时候。
什么不反人类?固然是同步代码啊!书写简单又易于阅读~
那怎么作到呢?其实借由生成器这么个东东咱们就可以实现啦。
那么,咱们须要先了解一下生成器是什么
生成生成,就是要生点什么,那么生成器
生了点什么呢?生成器实际上生成了迭代器
。
emmm...那迭代器又是个什么鬼呢?迭代器其实就是有next方法的对象,每次调用next方法都会返回一个data和一个标识符(用来标识是否已经迭代完毕)。
嗯,可能这么解释仍是不怎么清楚。其实生成器它自己是一个函数,或则说是一个集成的函数,它用*
来标识它本身,像这样function *gen(){}
,而后咱们每次调用迭代器的next方法的时候,生成器方法就会被执行一部分,只有咱们经过不断调用next,这个生成器方法才会被完全执行完成,并在最后一次next调用时返回done:false的标识。
咱们来看一个示例
function *r(){
let content1 = yield read('./1.txt','utf8');
let content2 = yield read(content1,'utf8');
return content2;
}
let it = r();
复制代码
其中*r
就是一个生成器函数,而it
就是这个生成器函数生成的迭代器。每一次it.next()
,生成函数都会执行一部分
it.next
时执行的代码,橘色的是第二次,红色的是第三次。
也就是说每次调用时以yield
为分界的,yield表明产出,它会以yield后面的部分做为next
调用时返回的value值。
另外还有点须要注意的是生成器里的yield左边的=
并不表明赋值运算,而表明调用next
时会接受一个参数传入做为输入,而content一、content2其实是做为参数传入的形参。
[warning] 注意: 第一次迭代是没法传入参数的,但生成器生成迭代器时能够接收参数做为输入。
最后生成器方法的return的值就是最后一次next
调用时返回的value值,而且此时的done为true。另外不是说今后以后不能再调next了,只是获得的对象永远都会是{value:undefined,done:true}
咱们的目的是为了使异步代码书写起来看起来像是同步代码同样
咱们知道生成器函数是分段执行的,且每次迭代都会接受一个参数做为输入,而后每次都会yield
产出。So咱们能利用它这种机制
function *r(p1){
console.log(p1)
let content1 = yield read('./1.txt','utf8');
let content2 = yield read(content1,'utf8');
return content2;
}
let it = r('生成迭代器时传入的参数');
//第一次迭代
it.next().value.then(function(data){ // 2.txt
//第二次迭代
it.next(data).value.then(function(data){
//第三次迭代,迭代完毕
console.log(it.next(data).value);
});
});
复制代码
上面的示例中,若是咱们只看*r
里面的内容,那么这样书写的形式几乎是和同步木有区别的。
那么,有没有一种方法可以让*r
下面那一团子代码在咱们在生成其中写完代码后就本身产生呢?
嗯,Co
的出现就是为了解决这个问题的,Co是TJ大姥姥写的一个库,能帮咱们自动生成迭代代码
function *read() {
console.log('开始');
let a = yield readFile('1.txt');
console.log(a);
let b = yield readFile('2.txt'); //执行这里时必然有一个le a的输入,就像是上一句代码当即获得了返回值同样
console.log(b);
let c = yield readFile('3.txt');
console.log(c);
return c;
}
//咱们只需在生成器里写完代码后再加上这么一句
co(read).then(function(data){
console.log(data); //data为成器函数中c的值
})
//---
function readFile(filename) {
return new Promise(function (resolve, reject) {
fs.readFile(filename, 'utf8', function (err, data) {
err ? reject(err) : resolve(data);
});
})
}
复制代码
那么这是怎么实现的呢?从代码量上来讲其实很简单,就几行代码,
function co(gen){ //传入一个生成器
let it = gen(); //生成一个迭代器
return new Promise((resolve,reject)=>{
!function next(lastVal){
//这里的next的lastVal参数即为上一次迭代出的promise的结果,也是a的值,而后依次类推...
let{value,done} = it.next(lastVal);
if(done) {
resolve(value); //若是生成器函数执行完成就让co的promise成功
}else{ //若是尚未迭代完,在这次返回的promise中绑定回调,当状态改变时调用下一次迭代
value.then(next,reject);
}
}()
})
}
// 效果等同于前文所说的
//第一次迭代
it.next().value.then(function(data){ // 2.txt
//第二次迭代
it.next(data).value.then(function(data){
//第三次迭代,迭代完毕
console.log(it.next(data).value);
});
});
复制代码
思路分析: yield readFile('1.txt')
执行完毕,会等待下一次迭代和let a
的输入,而等到何时呢?会等到readFile这个异步函数获得结果后才会继续走。这时let a
对于yield readFile('2.txt')
是有效的,就像同步代码中当即获得了返回值同样。
通过上面一遭咱们终于可以像写同步代码同样写异步了,但美中不足的是每次在咱们在生成器中写完异步代码,都须要在最后用Co来生成对应的迭代代码,那有没有更简单的方法呢?嗯。。。有的!
Async/Await 其实是 promise+迭代器实现的语法糖,常和bluebird
promise实现库 结合起来使用,号称异步的终极解决方案。
let Promise = require('bluebird');
let readFile = Promise.promisify(require('fs').readFile);
async function read() {
//await后面必须跟一个promise,
let a = await readFile('./1.txt','utf8');
console.log(a);
let b = await readFile('./2.txt','utf8');
console.log(b);
let c = await readFile('./3.txt','utf8');
console.log(c);
return 'ok';
}
read().then(data => {
console.log(data);
});
复制代码
抛去语法糖的糖衣,其实就是对Co进行了一层封装
//co实现
function read(){
return co(function *(){
let a = yield readFile('./1.txt');
console.log(a);
let b = yield readFile('./2.txt');
console.log(b);
let c = yield readFile('./3.txt');
console.log(c);
return 'ok';
});
}
复制代码
到此为止,咱们终于走完了异步编程10年发展的慢慢长路,鼓掌!!!
参考资料: