众所周知Javascript
是“单线程”语言,在实际开发中咱们又不得不面临异步逻辑的处理,这时候异步编程就变得十分必要。所谓异步,就是指在执行一件任务,这件任务分A、B两个阶段,执行完A阶段后,须要去作另一个任务获得结果后才能执行B阶段。异步编程有如下几种经常使用方式:callback
、Promise
、Generator
、async
。html
callback函数是指经过函数传参
传递到其余执行代码的,某一块可执行代码的引用,被主函数调用后又回到主函数,以下例:程序员
function add(a, b, callback){
var num = a + b;
callback(num)
}
add(1, 2, function(num){
console.log(num); # 3
# ...
})
复制代码
若是是有个任务队列,里面包含多个任务的话,那就须要层层嵌套了es6
var readFile = require('fs-readfile-promise'); # 读取文件函数
readFile(fileA, function(data) {
readFile(fileB, function(data) {
# ...
})
})
复制代码
如上若是我存在n个任务,那须要层层嵌套n层,这样代码显得很是冗余庞杂而且耦合度很高,修改其中某一个函数的话,会影响上下函数代码块的逻辑。这种状况被称为“回调地狱”(callback hell)
编程
Promise是咱们经常使用来解决异步回调问题的方法。容许将回调函数的嵌套,改成链式调用。以上多个任务的话,能够改形成以下例子:json
function add(a, b){
return new Promise((resolve, reject) => {
var result = a+b;
resolve(result);
})
}
add(10, 20).then(res => {
return add(res, 20) # res = 30
}).then(res => {
return add(res, 20) # res = 50
}).then(res => {
// ...
}).catch(err => {
// 错误处理
})
复制代码
add函数执行后会返回一个Promise
,它的结果会进入then方法中,第一个参数是Promise
的resolve
结果,第二个参数(可选)是Promise
的reject
结果。咱们能够把回调后的逻辑在then
方法中写,这样的链式写法有效的将各个事件的回调处理分割开来,使得代码结构更加清晰。另外咱们能够在catch
中处理报错。数组
若是是咱们的异步请求不是按照顺序A->B->C->D这种,而是[A,B,C]->D,先并行执行A、B、C完而后在执行D,咱们能够用Promise.all();promise
# 生成一个Promise对象的数组
const promises = [2, 3, 5].map(function (id) {
return getJSON('/post/' + id + ".json"); # getJSON 是返回被Promise包装的数据请求函数
});
Promise.all(promises).then(function (posts) {
# promises里面装了三个Promise
# posts返回的是一个数组,对应三个Promise的返回数据
# 在这能够执行D任务
}).then(res => {
//...
}).catch(function(reason){
//...
});
复制代码
可是Promise
的代码仍是有些多余的代码,好比被Promise
包装的函数有一堆new Promise
、then
、catch
。bash
Generator函数是ES6提供的一种异步编程解决方案,由每执行一次函数返回的是一个遍历器对象,返回的对象能够依次遍历Generator里面的每一个状态,咱们须要用遍历器对象的next
方法来执行函数。并发
先来个例子:异步
function* foo() {
yield 'stepone';
yield 'steptwo';
return 'stepthree';
}
var _foo = foo();
_foo.next(); #{value: 'stepone', done: false}
_foo.next(); #{value: 'stepone', done: false}
_foo.next(); #{value: 'stepone', done: false}
复制代码
Generator有三个特征:函数命名时function
后面须要加*
;函数内部有yield
;外部执行须要调用next
方法。每一个yield会将跟在她后面的值包裹成一个对象的返回,返回的对象中包括返回值和函数运行状态,直到return
,返回done
为true
。
若是每次运行Generator函数咱们都须要用next的话,你那就太麻烦了,咱们须要一个能够自动执行器。co 模块是著名程序员 TJ Holowaychuk 于 2013 年 6 月发布的一个小工具,用于 Generator 函数的自动执行。 运用co模块时,yield后面只能是 Thunk函数 或者Promise对象,co函数执行完成以后返回的是Promise。以下:
var co = require('co');
var gen = function* () {
var img1 = yield getImage('/image01');
var img2 = yield getImage('/image02');
...
};
co(gen).then(function (res){
console.log(res);
}).catch(err){
# 错误处理
};
复制代码
co模块的任务的并行处理,等多个任务并行执行完成以后再进行下一步操做:
# 数组的写法
co(function* () {
var res = yield [
Promise.resolve(1),
Promise.resolve(2)
];
console.log(res);
}).then(console.log).catch(onerror);
# 对象的写法
co(function* () {
var res = yield {
1: Promise.resolve(1),
2: Promise.resolve(2),
};
console.log(res);
}).then(console.log).catch(onerror);
复制代码
Generator
函数虽然相比Promise
在写法上更加精简且逻辑清晰,可是须要额外有个运行co
函数去执行,为了解决优化这个问题,async
函数出现了。
async函数是Generator
函数的语法糖。
var co = require('co');
var gen = function* () {
var img1 = yield getImage('/image01');
var img2 = yield getImage('/image02');
...
};
co(gen).then(function (res){
console.log(res);
}).catch(err){
# 错误处理
};
****
#以上Generator函数能够改成
var gen = async function () {
var img1 = await getImage('/image01');
var img2 = await getImage('/image02');
return [img1, img2];
...
};
gen().then(res => {
console.log(res) # [img1, img2]
});
复制代码
相比Generator
函数,async
函数在写法上的区别就是async
替代了*
,await
替代了yield
,而且async
自带执行器,只需gen()便可执行函数;拥有比较好的适应性,await
后面能够是Promise
也能够是原始类型的值;此外async
函数返回的是Promise
,便于咱们更好的处理返回值。
async function gen() {
return '111';
# 等同于 return await '111';
};
gen().then(res => {
console.log(res) # 111
});
复制代码
若是是直接return值,这个值会自动成为then方法回调函数中的值。
async function gen() {
var a = await getA();
var b = await getB();
return a + b;
};
gen().then(res => {
console.log(res)
});
复制代码
async
函数返回的Promise
,必须等到函数体内全部await
后面的Promise
对象都执行完毕后,或者return
或者抛错
以后才能改变状态;也就是只有async
里面的异步操做所有操做完,才能回到主任务来,而且在then
方法里面继续执行主任务。
# 错误处理1
async function gen() {
await new Promise((resolve, reject) => {
throw new Error('出错了');
})
};
gen().then(res => {
console.log(res)
}).catch(err => {
console.log(err) # 出错了
});
# 错误处理2:以下处理,一个await任务的错误不会影响到后面await任务的执行
async function gen() {
try{
await new Promise((resolve, reject) => {
throw new Error('出错了');
})
}catch(e){
console.log(e); # 出错了
}
return Promise.resolve(1);
};
gen().then(res => {
console.log(res) # 1
});
复制代码
错误处理如上。
async function gen() {
# 写法一
let result = await Promise.all([getName(), getAddress()]);
return result;
# 写法二
let namePromise = getName();
let addressPromise = getAddress();
let name = await namePromise;
let address = await addressPromise;
return [name, address];
};
gen().then(res => {
console.log(res); # 一个数组,分别是getName和getAddress返回值
})
复制代码
多个异步任务互相没有依赖关系,须要并发时,可按照如上两种方法书写。
function chainAnimationsPromise(elem, animations) {
# 变量ret用来保存上一个动画的返回值
let ret = null;
# 新建一个空的Promise
let p = Promise.resolve();
# 使用then方法,添加全部动画
for(let anim of animations) {
p = p.then(function(val) {
ret = val;
return anim(elem);
});
}
# 返回一个部署了错误捕捉机制的Promise
return p.catch(function(e) {
# 错误处理
}).then(function() {
return ret;
});
}
复制代码
Promise
虽然很好的解决了地狱回调的问题,可是代码中有不少与语义无关的then
、catch
等;
function chainAnimationsGenerator(elem, animations) {
return co(function*() {
let ret = null;
try {
for(let anim of animations) {
ret = yield anim(elem);
}
} catch(e) {
# 错误处理
}
return ret;
});
}
复制代码
Generator
函数须要自动执行器来执行函数,且yield
后面只能是Promise
对象或者Thunk
函数。
async function chainAnimationsAsync(elem, animations) {
let ret = null;
try {
for(let anim of animations) {
ret = await anim(elem);
}
} catch(e) {
# 错误处理
}
return ret;
}
复制代码
async
函数的实现最简洁,最符合语义,几乎没有语义不相关的代码。与Generator
相比不须要程序员再提供一个执行器,async
自己自动执行,使用起来方便简洁。