深入探析koa之异步回调处理篇
在上一篇中我们梳理了koa当中中间件的洋葱模型执行原理,并实现了一个可以让洋葱模型自动跑起来的流程管理函数。这一篇,我们再来研究一下koa当中异步回调同步化写法的原理,同样的,我们也会实现一个管理函数,是的我们能够通过同步化的写法来写异步回调函数。
1. 回调金字塔及理想中的解决方案
我们都知道javascript是一门单线程异步非阻塞语言。异步非阻塞当然是它的一个优点,但大量的异步操作必然涉及大量的回调函数,特别是当异步嵌套的时候,就会出现回调金字塔的问题,使得代码的可读性非常差。比如下面一个例子:
var fs = require('fs');
fs.readFile('./file1', function(err, data) {
console.log(data.toString());
fs.readFile('./file2', function(err, data) {
console.log(data.toString());
})
})
这个例子是先后读取两个文件内容并打印,其中file2的读取必须在file1读取结束之后再进行,因此其操作必须要在file1读取的回调函数中执行。这是一个典型的回调嵌套,并且只有两层而已,在实际编程中,我们可能会遇到更多层的嵌套,这样的代码写法无疑是不够优雅的。
在我们想象中,比较优雅的一种写法应该是看似同步实则异步的写法,类似下面这样:
var data;
data = readFile('./file1');
//下面的代码是第一个readFile执行完毕之后的回调部分
console.log(data.toString());
//下面的代码是第二个readFile的回调
data = readFile('./file2');
console.log(data.toString());
这样的写法,就完全避免回调地狱。事实上,koa就让我们可以使用这样的写法来写异步回调函数:
var koa = require('koa');
var app = koa();
var request=require('some module');
app.use(function*() {
var data = yield request('http://www.baidu.com');
//以下是异步回调部分
this.body = data.toString();
})
app.listen(3000);
那么,究竟是什么让koa有这么神奇的魔力呢?
2. generator配合promise实现异步回调同步写法
关键的一点,其实前一篇也提到了,就是generator具有类似"打断点"这样的效果。当遇到yield的时候,就会暂停,将控制权交给yield后面的函数,当下次返回的时候,再继续执行。
而在上面的那个koa例子中,yield后面的可不是任何对象都可以哦!必须是特定类型。在co函数中,可以支持promise, thunk函数等。
今天的文章中,我们就以promise为例来进行分析,看看如何使用generator和promise配合,实现异步同步化。
依旧以第一个读取文件例子来分析。首先,我们需要将读文件的函数进行改造,将其封装成为一个promise对象:
var fs = require('fs');
var readFile = function(fileName) {
return new Promise(function(resolve, reject) {
fs.readFile(fileName, function(err, data) {
if (err) {
reject(err);
} else {
resolve(data);
}
})
})
}
//下面是readFile使用的示例
var tmp = readFile('./file1');
tmp.then(function(data) {
console.log(data.toString());
})
关于promise的使用,如果不熟悉的可以去看看es6中的语法。(近期我也会写一篇文章来教大家如何用es5的语法来自己实现一个具备基本功能的promise对象,敬请期待呦^_^)
简单来讲,promise可以实现将回调函数通过 promise.then(callback)的形式来写。但是我们的目标是配合generator,真正实现如丝般顺滑的同步化写法,如何配合呢,看这段代码:
var fs = require('fs');
var readFile = function(fileName) {
return new Promise(function(resolve, reject) {
fs.readFile(fileName, function(err, data) {
if (err) {
reject(err);
} else {
resolve(data);
}
})
})
}
//将读文件的过程放在generator中
var gen = function*() {
var data = yield readFile('./file1');
console.log(data.toString());
data = yield readFile('./file2');
console.log(data.toString());
}
//手动执行generator
var g = gen();
var another = g.next();
//another.value就是返回的promise对象
another.value.then(function(data) {
//再次调用g.next从断点处执行generator,并将data作为参数传回
var another2 = g.next(data);
another2.value.then(function(data) {
g.next(data);
})
})
上述代码中,我们在generator中yield了readFile,回调语句代码写在yield之后的代码中,完全是同步的写法,实现了文章一开头的设想。
而yield之后,我们得到的是一个another.value是一个promise对象,我们可以使用then语句定义回调函数,函数的内容呢,则是将读取到的data返回给generator并继续让generator从断点处执行。
基本上这就是异步回调同步化最核心的原理,事实上如果大家熟悉python,会知道python中有"协程"的概念,基本上也是使用generator来实现的(我想当怀疑es6的generator就是借鉴了python~)
不过呢,上述代码我们依然是手动执行的。那么同上一篇一样,我们还需要实现一个run函数,用于管理generator的流程,让它能够自动跑起来!
3. 让同步化回调函数自动跑起来:一个run函数的编写
仔细观察上一段代码中手动执行generator的部分,也能发现一个规律,这个规律让我们可以直接写一个递归的函数来代替:
var run=function(gen){
var g;
if(typeof gen.next==='function'){
g=gen;
}else{
g=gen();
}
function next(data){
var tmp=g.next(data);
if(tmp.done){
return ;
}else{
tmp.value.then(next);
}
}
next();
}
函数接收一个generator,并让其中的异步能够自动执行。使用这个run函数,我们来让上一个异步代码自动执行:
var fs = require('fs');
var run = function(gen) {
var g;
if (typeof gen.next === 'function') {
g = gen;
} else {
g = gen();
}
function next(data) {
var tmp = g.next(data);
if (tmp.done) {
return;
} else {
tmp.value.then(next);
}
}
next();
}
var readFile = function(fileName) {
return new Promise(function(resolve, reject) {
fs.readFile(fileName, function(err, data) {
if (err) {
reject(err);
} else {
resolve(data);
}
})
})
}
//将读文件的过程放在generator中
var gen = function*() {
var data = yield readFile('./file1');
console.log(data.toString());
data = yield readFile('./file2');
console.log(data.toString());
}
//下面只需要将gen放入run当中即可自动执行
run(gen);
执行上述代码,即可看到终端依次打印出了file1和file2的内容。
需要指出的是,这里的run函数为了简单起见只支持promise,而实际的co函数还支持thunk等。
这样一来,co函数的两大功能基本就完整介绍了,一个是洋葱模型的流程控制,另一个是异步同步化代码的自动执行。在下一篇文章中,我将带大家对这两个功能进行整合,写出我们自己的一个co函数!
这篇文章的代码同样可以在github上面找到:https://github.com/mly-zju/async-js-demo,其中promise_generator.js就是本篇的示例源码。
同样欢迎大家多多关注我的github pages个人博客哦,会不定期更新我的技术文章~
深入探析koa之异步回调处理篇的更多相关文章
- 深入探析koa之中间件流程控制篇
koa被认为是第二代web后端开发框架,相比于前代express而言,其最大的特色无疑就是解决了回调金字塔的问题,让异步的写法更加的简洁.在使用koa的过程中,其实一直比较好奇koa内部的实现机理.最 ...
- 浏览器环境下Javascript脚本加载与执行探析之DOMContentLoaded
在”浏览器环境下Javascript脚本加载与执行探析“系列文章的前几篇,分别针对浏览器环境下JavaScript加载与执行相关的知识点或者属性进行了探究,感兴趣的同学可以先行阅读前几篇文章,了解相关 ...
- 浏览器环境下JavaScript脚本加载与执行探析之动态脚本与Ajax脚本注入
在<浏览器环境下JavaScript脚本加载与执行探析之defer与async特性>中,我们研究了延迟脚本(defer)和异步脚本(async)的执行时机.浏览器支持情况.浏览器bug以及 ...
- 如何优雅的处理Nodejs中的异步回调
前言 Nodejs最大的亮点就在于事件驱动, 非阻塞I/O 模型,这使得Nodejs具有很强的并发处理能力,非常适合编写网络应用.在Nodejs中大部分的I/O操作几乎都是异步的,也就是我们处理I/O ...
- 浏览器环境下JavaScript脚本加载与执行探析之defer与async特性
defer和async特性相信是很多JavaScript开发者"熟悉而又不熟悉"的两个特性,从字面上来看,二者的功能很好理解,分别是"延迟脚本"和"异 ...
- 中文分词工具探析(二):Jieba
1. 前言 Jieba是由fxsjy大神开源的一款中文分词工具,一款属于工业界的分词工具--模型易用简单.代码清晰可读,推荐有志学习NLP或Python的读一下源码.与采用分词模型Bigram + H ...
- 中文分词工具探析(一):ICTCLAS (NLPIR)
1. 前言 ICTCLAS是张华平在2000年推出的中文分词系统,于2009年更名为NLPIR.ICTCLAS是中文分词界元老级工具了,作者开放出了free版本的源代码(1.0整理版本在此). 作者在 ...
- node 异步回调解决方法之yield
先看如何使用 使用的npm包为genny,npm 安装genny,使用 node -harmony 文件(-harmony 为使用es6属性启动参数) 启动项目 var genny= require( ...
- c#线程之异步委托begininvoke、invoke、AsyncWaitHandle.WaitOne 、异步回调
单靠自己看书学总是会走很多弯路,任何人也不列外,有些时候自己遇到的很多问题,其它别人在很久之前也可能遇到过,上网查查可以走很大捷径,对自己的学习有很大帮助,刚开始弄线程这块,一开始只是看书,很多东西都 ...
随机推荐
- Android闹钟【复杂版】
最近做闹钟,所以自己写了个Demo版本,这个程序是用listview单独的类来实现的,和activity类分开来实现的!这个是用数据库进行更新的,当闹钟设置后,闹钟图片变成闹钟的样子,闹钟取消后,图片 ...
- 剑指 offer set 8 树的子结构
总结 1. 解法分为两步, 一是定位树的头结点, 二是两棵树作比较 2. 两个数作比较, 包括比较两棵树是否相等, 或者两个数是否镜像, 算法的框架类似 bool comp(root1, root2)
- ubantu 安装mysql
sudo apt-get install mysql-server mysql-client
- C#分屏控件用法实例
本文实例中的自定义类PictureBox继承于UserControl,最终实现简单的分屏功能.分享给大家供大家参考之用.具体实现代码如下: public partial class PictureCo ...
- SQL Server 的事务和锁(一)
最近在项目中进行压力测试遇到了数据库的死锁问题,简言之,如下的代码在 SERIALIZABLE 隔离级别造成了死锁: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 SELECT @ ...
- EF——继承映射关系TPH、TPT和TPC的讲解以及一些具体的例子 05 (转)
EF里的继承映射关系TPH.TPT和TPC的讲解以及一些具体的例子 本章节讲解EF里的继承映射关系,分为TPH.TPT.TPC.具体: 1.TPH:Table Per Hierarchy 这是EF ...
- 美国H1B基本情况及相关数据
H-1B是外国人在美国工作的签证,从美国学校毕业之后会有12个月的OPT实习期,在OPT结束之前必须申请到H1B签证才能在实习期结束后继续工作. H1B需要由雇佣你的公司帮你申请,也就是说必须先找到工 ...
- 1.5.1 Analyzers,Tokenizers,Filters概述
字段分析器(Analyzers)即用于文档索引也用于查询.一个分析器检查字段的文本,并生成一个token流.分析器可能是一个单独的类,也可能是一系列的tokenizer和filter的组合. 分词器把 ...
- Android动画 interpolator的用法
1. <?xml version="1.0" encoding="utf-8"?> 2. <set 3. xmlns:Android=&quo ...
- 通过 CALayer 代理方法绘图
#import "ViewController.h" #define WIDTH 50 #define PHOTO_HEIGHT 150 @interface ViewContro ...