在JavaScript中做异步开发时,我们现在会毫不犹豫的使用`Async/Await`。不管是并发还是串行,`Async/Await`都能处理的很好,而且还保证了代码的可读性。但是,`Async/Await`是在ES6之后突然出现的, 这激发了我的好奇心,使得我想更进一步的了解它。本文假设读者是对Promise和Async有一定了解的,因此对基础部分不做过多的叙述。然后,我将通过以下三个部分来剖析`Async\Await`:
* Generator函数
* 自动执行器
* 内置执行
`Async`函数的实现原理就是将Generator函数和自动执行器包装在一个函数里。
### Generator函数
### 基本用法
Generator函数是ES6提供的一种异步编程解决方案,它的调用方式与普通函数一样,也是在函数后面加上一对圆括号。不同的是,调用Generator函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象(遍历器对象)。接着,必须调用遍历器对象的next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一条yield语句(或return语句)为止。
~~~js
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
var hw = helloWorldGenerator();
hw.next(); // { value: 'hello', done: false }
hw.next(); // { value: 'world', done: false }
hw.next(); // { value: 'ending', done: true }
hw.next(); // { value: undefined, done: true }
~~~
### 异步操作
Ajax是典型的异步操作,我们试着通过Generator函数部署Ajax,来达到同步表达的效果。
~~~js
function* main() {
var result = yield request("url");
var res = JSON.parse(result);
console.log(res.value)
}
function request(url) {
makeAjaxCall(url, function(response) {
it.next(response);
});
}
var it = main();
it.next();
~~~
这样似乎能满足我们的要求,但如果我们增加了请求的数量,那结果就不是我们期望的那样美好了:我们需要不断的调用next方法来逐个执行异步请求。如果Generator能够自动执行就好了。
~~~js
function main() {
var result1 = yield request('url1');
var res1 = JSON.parse(result1);
console.log(res1.value);
var result2 = yield request('url2');
var res2 = JSON.parse(result2);
console.log(res2.value)
}
function request(url) {
makeAjaxCall(url, function(response) {
it.next(response);
});
}
var it = main();
it.next();
it.next();
~~~
### 自动执行器
如果要自动执行,我们首先就会想到用循环来处理。
~~~js
function* gen() [
......
}
var g = gen();
var res = g.next();
while(!res.done) {
console.log(res.value);
res = g.next();
}
~~~
上面的代码中,`gen`自动执行完所有步骤,但对于异步操作而言,必须保证前一步执行完才能执行后一步。根据经验,JavaScript中的回调函数和Promise刚好满足这个要求。
### 基于Thunk(回调函数)的自动执行器
JavaScript中`Thunk`函数是用来将多参函数替换成只接受回调函数作为参数的单参函数。通过`Thunk`函数,我们可以将`next`方法作为回调函数传给 需要异步操作的方法,当异步操作完成时,调用`next`方法从而执行下一个操作,从而达到异步操作同步执行的效果。
~~~js
var fs = require('fs');
var thunk = function(fn) {
return function() {
var args = Array.prototype.slice.call(arguments);
return function(callback) {
args.push(callback);
return fn.apply(this, args);
}
}
};
var readFileThunk = thunk(fs.readFile);
function run(fn) {
var gen = fn();
function next(err, data) {
var result = gen.next(data);
if(result.done) return;
result.value(next);
}
next();
}
function* g() {
var f1 = yield readFile('fileA');
var f2 = yield readFile('fileB');
// ...
var fn = yield readFile('fileN');
}
run(g);
~~~
### 基于Promise对象的自动执行器
除了thunk之外,Promise也可以实现自动执行器。这其中的原理也是类似的,也是在确保异步操作完成的情况下调用`next`方法,从而执行下一次的异步操作。
~~~js
var fs = require('fs');
var readFile = function(fileName) {
return new Promise(function(resolve, reject) {
fs.readFile(fileName, function(resolve, reject) {
if(error) return reject(error);
resolve(data);
});
});
};
var gen = function* () {
var f1 = yield readFile('/etc/fstab');
var f2 = yield readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
}
function run(gen) {
var g = gen();
function next(data) {
var result = g.next(data);
if(result.done) return result.value;
result.value.then(function(data) {
next(data);
});
}
next();
}
run(gen);
~~~
### 内置执行
前面我们通过Generator函数写了读取文件的代码,接下来我们将它们改成async函数的形式。
~~~js
......
var asyncReadFile = async function() {
var f1 = await readFile('/etc/fstab');
var f2 = await readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString())
}
~~~
通过比较就会发现,async函数就是将Generator函数的星号(\*)替换成async,将yield替换成await,然后内置了一个功能类似`run`的执行器。我们用代码模拟下一下这个执行器。
~~~js
async function fn(args) {
//......
}
// 等同于
function fn(args) {
return spawn(function* () {
//......
});
}
function spawn(genF) {
return new Promise(function(resolve, reject) {
var gen = genF();
function step(nextF) {
try {
var next = nextF();
} catch(e) {
return reject(e);
}
if(next.done) {
return resolve(next.value);
}
Promise.resovle(next.value).then(function(v) {
step(function(){
return gen.next(v);
});
}, function(e){
step(function() {
return gen.throw(e);
});
});
}
step(function() {
return gen.next(undefined);
});
});
}
~~~
### 总结
本文从Generator函数、自动执行器再到内置执行,分拆解析了`Async`的原理,希望能对大家有所帮助。
- 开篇卷
- 一.koa基础
- 1.koa基础之开发环境搭建
- 2.koa基础之路由
- 3.koa基础之路由另一种写法
- 4.koa基础之get 传值 以及获取 get 传值
- 5.koa 基础之动态路由的传值
- 6.koa基础之ejs模板的使用
- 7.koa基础之From表单提交get与post数据
- 8.koa基础之koa-bodyparser 中间件获取表单提交的数据
- 9.koa基础之koa-static 静态资源中间件 静态web服务
- 10.koa基础之koa-art-template 模板引擎的使用
- 11.koa基础之cookie 的基本使用
- 12.koa基础之koa中session的使用
- 13.koa基础之重定向
- 二.koa进阶
- koa对文件操作
- 上传文件
- 上传单个文件
- 上传多个文件
- 下载文件
- 下载单个文件
- 下载多个文件
- 参考文章
- koa模块化路由
- koa 允许跨域
- koa 应用生成器
- koa对数据库操作
- koa对mongodb的操作
- koa对redis的操作
- koa对mysql的操作
- koa对sqlite操作
- koa与elasticsearch的操作
- koa与PostgreSQL的操作
- koa与Neo4j的操作
- koa-static
- koa的async与await使用
- koa模板引擎
- art-template
- ejs模板引擎
- koa-jsonp使用
- 分页 jqPaginator_koa
- Koa2 ueditor
- koa-multer
- koa-session
- koa-cors
- koa全局变量定义
- koa-compress中间件
- 全球公用头像的使用
- token生成
- koa-passport
- Koa RESTful Api接口
- Koa中集成GraphQl实现 Server API 接口
- koa集成Swagger
- koa 二维码的实现
- 三.koa实战
- 一.koa与IM实战
- koa和websocket实战
- koa与Socket.io实战
- koa与WebRTC实战
- 二.koa与Web实战
- 三.koa与react实战
- 四.koa与vue实战
- 五.微信公众号开发
- 四.koa微服务
- 微服务框架
- Tars.js
- Seneca.js
- dubbo.ts
- 番外篇
- koa开发环境搭建
- Koa中间件
- koa中间件的执行顺序
- 浅谈koa中间件的实现原理
- async和await详解
- Async/Await原理解析
- koa文章参考
- 其他参考
- 网上学习资源
- json-server
- Jenkins打包指南
- 前端工作流规范
- 结束篇