async是es7提出的一种新的异步语法. 一开始es为了解决异步,使用的是promise, 但看到满屏的then之后,就感觉自己傻逼了. 后来提出了generator, 在底层实现了一个异步的模式, 但需要手动执行. 关于如何使用generator,可以参考,how to use generator. 本文这里,不探讨怎么使用generato. 而是,如果使用generator和promise 构造出async的表达. 文末后面会介绍如何正式的使用 async/await
,以及里面有什么需要注意的内容。
一般异步的写法就是,传回调嘛,比如:
var ajax = function(url,cb){
// send url
// get res
...
cb(JSON.parse(result))
}
这样,应该最容易写成callback hell. 然后我们引入: generator
function *main() {
var result1 = yield ajax( "http://some.url.1" );
var data = JSON.parse( result1 );
var result2 = yield ajax( "http://some.url.2?id=" + data.id );
var resp = JSON.parse( result2 );
}
var async = main();
async.next();
function ajax(url){
// send url
async.next(res);
}
伪代码的格式如上所述. 这里,需要主意一个点. 如果,你没有在next()中传入res,like async.next()
. 那么,里面result1和result2获得的结果就是undefined. 上面就是基本的generator异步. 那如果使用generator来模拟async呢? 这估计得解释一下async出现的原因
根据上面的解析,我们可以了解到, 使用next 执行语句时, 只能执行yield后面的表达式. 这样造成的结果就是,不能parallel异步呀. 这样限制性还是很大的。所以,为了解决这个问题,使用到es6提出的Promise对象来进行补充. 这里,增加一个限定规则,即,ajax拉取返回的必须是一个promise对象.
function ajax(url){
return new Promise(function(res,rej){
send(url,function(result){
res(result)
})
})
}
我们再补充一下,如果使用generator来对promise进行tricky
function runGenerator(g) {
var it = g(), ret;
(function iterate(val){
ret = it.next( val );
if (!ret.done) {
// 检查是否已经then完成
if ("then" in ret.value) {
// 这一句很关键
ret.value.then( iterate );
}
else {
// 同步回调的trick
setTimeout( function(){
iterate( ret.value );
}, 0 );
}
}
})();
}
OK, 这样, 我们就可以在async里面,使用同步的写法,来代表异步的操作了.
runGenerator(function* (){
var result = yield new Promise(function(res,rej){
setTimeout(function(){
res('ok');
},1000)
});
})
由于这里要求的是使用promise, 那么,我们使用Promise.all([xx,xx]) 也是合情合理的. 这样就可以完美的解决掉--并行异步发送。
runGenerator(function* (){
yield Promise.all([ajax('http://villainhr.com'),ajax('http://villainhr.com')])
})
对比与,使用async的结构:
(async function(){
await Promise.all([ajax('http://villainhr.com'),ajax('http://villainhr.com')]);
})();
是不是感觉一毛一样呢? 不过在实际上操作中, async 还必须对new Promise进行兼容处理. 如果其他人直接传入一个expression, 你也必须保证他是可行的. 在babel中,讲的其实也是这样一个逻辑:
// In
async function foo() {
await bar();
}
// Out
var _asyncToGenerator = function (fn) {
...
};
var foo = _asyncToGenerator(function* () {
yield bar();
});
具体参考: babel es6 转码
前面已经基本介绍如何使用 async
这里,简单介绍一下如何在工程中接入 async
。这里,我们以 webpack
为例,只需要额外下载 stage-3
,并修改配置即可。
# 下载 stage-3
npm install babel-preset-stage-3 --save-dev
# 修改 config 配置
module: {
loaders: [{
test: /\.jsx?$/,
include: [
path.resolve(__dirname, 'src')
],
loader: 'babel-loader',
query: {
presets: ['es2015', 'stage-3', 'react'],
}
}]
}
不过,上面那种是针对浏览器环境比较好的条件下,一般来说,如果针对一些低版本浏览器,还需要使用 stage-0
的配置,那么写法就应该改成。
entry: ['babel-polyfill', './test.js'],
loaders: [
{ test: /\.jsx?$/, loader: 'babel', exclude:/node_modules/,
presets: ['es2015','stage-0', 'react'],
}
]
在具体使用 async 时,会遇到各种使用 case,这里我们按照顺序,来简单的描述一下。
在使用 await
时,需要注意,其修饰的就是一个 Promise 对象 或者 async 函数,不能修饰非异步对象。并且,使用 await 时,外部块级作用域一定需要使用 async
进行包裹。
function promiseFunc () {
return fetch('https://api.github.com/whatever')
.then((data) => {
return data.status;
});
}
async function AsyncFunc () {
const data = await promiseFunc ();
console.log(data);
return data;
}
async function wrap() {
const data = await AsyncFunc ();
console.log(data);
}
使用 async 时,捕获 Promise 中的错误写法有两种,一种是直接使用 try-catch
进行捕获,一种是直接通过 catch()
来捕获。
async function myFunction() {
try {
await somethingThatReturnsAPromise();
} catch (err) {
console.log(err);
}
}
// 另一种写法
async function myFunction() {
await somethingThatReturnsAPromise().catch(function (err){
console.log(err);
});
}
使用并行 async 时,可以直接利用 Promise.all
执行即可:
const [foo, bar] = await Promise.all([getFoo(), getBar()]);
正常情况下,使用 await
是需要包裹在 async
这样才能利用 generator
来暂停当前块级作用域。否则的话,他会直接按照异步的模式,顺序执行。
async A(v) {
console.log('class A');
return await Promise.resolve();
}
async B(foo) {
console.log('class B');
return await Promise.resolve();
}
A().then(()=>{console.log("1")})
B().then(()=>{console.log("2")})
# 只会输出
A
B
1
2
如果要保证顺序执行的话,则需要使用 async 进行包裹。
(asycn function test(){
await A();
console.log("1")
await B();
console.log("2")
})()
有时候需要了解一个浏览器是否支持 async ,然后针对不同的异步写法来做相关的兼容。这时,异步检测特性就显得极为重要。那如何快速用同步的方式在浏览器里面检测是否支持 async,可以直接通过 newFunction(xxx)
的方式来做。直接看代码吧:
function asyncDetect(){
try {
new Function('async function test() {}')();
} catch (e) {
return false;
}
return true;
}
如果你需要检测是否支持 generator
的话,可以直接使用:
function generatorDetect (){
try {
new Function('function* test() {}')();
} catch (e) {
return false;
}
return true;
}
async/await 使用
async rejected 的使用