深入解析koa之异步回调处理


Posted in Javascript onJune 17, 2019

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就是本篇的示例源码。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
深入理解Javascript动态方法调用与参数修改的问题
Dec 10 Javascript
bootstrap table 服务器端分页例子分享
Feb 10 Javascript
浅谈javascript的Touch事件
Sep 27 Javascript
基于jQuery实现点击最后一行实现行自增效果的表格
Jan 12 Javascript
js动态生成form 并用ajax方式提交的实现方法
Sep 09 Javascript
javascript学习笔记_浅谈基础语法,类型,变量
Sep 19 Javascript
Vue.js devtool插件安装后无法使用的解决办法
Nov 27 Javascript
浅析Angular19 自定义表单控件
Jan 31 Javascript
AngularJs分页插件使用详解
Jun 30 Javascript
JS加密插件CryptoJS实现AES加密操作示例
Aug 16 Javascript
修改vue源码实现动态路由缓存的方法
Jan 21 Javascript
浅谈vant组件Picker 选择器选单选问题
Nov 04 Javascript
JS中数据结构与算法---排序算法(Sort Algorithm)实例详解
Jun 17 #Javascript
详解element-ui设置下拉选择切换必填和非必填
Jun 17 #Javascript
JS实现的字符串数组去重功能小结
Jun 17 #Javascript
JavaScript静态作用域和动态作用域实例详解
Jun 17 #Javascript
深入解析koa之中间件流程控制
Jun 17 #Javascript
深入解读Node.js中的koa源码
Jun 17 #Javascript
学习RxJS之JavaScript框架Cycle.js
Jun 17 #Javascript
You might like
php与XML、XSLT、Mysql的结合运用实现代码
2009/11/19 PHP
php截取html字符串及自动补全html标签的方法
2015/01/15 PHP
php递归遍历删除文件的方法
2015/04/17 PHP
php 修改上传文件大小限制实例详解
2016/10/23 PHP
php文件上传原理与实现方法详解
2019/12/20 PHP
php判断数组是否为空的实例方法
2020/05/10 PHP
个人总结的一些关于String、Function、Array的属性和用法
2007/01/10 Javascript
利用js实现选项卡的特别效果的实例
2013/03/03 Javascript
javascript计算星座属相(十二生肖属相)示例代码
2014/01/09 Javascript
jQuery实现右键菜单、遮罩等效果代码
2016/09/27 Javascript
使用BootStrap建立响应式网页——通栏轮播图(carousel)
2016/12/21 Javascript
JS+html5制作简单音乐播放器
2020/09/13 Javascript
JavaScript hasOwnProperty() 函数实例详解
2017/08/04 Javascript
在vue2.0中引用element-ui组件库的方法
2018/06/21 Javascript
jQuery选择器选中最后一个元素,倒数第二个元素操作示例
2018/12/10 jQuery
js实现课堂随机点名系统
2019/11/21 Javascript
15分钟上手vue3.0(小结)
2020/05/20 Javascript
基于Vant UI框架实现时间段选择器
2020/12/24 Javascript
JavaScript canvas实现文字时钟
2021/01/10 Javascript
[01:14]DOTA2亚洲邀请赛小组赛赛前花絮
2017/03/27 DOTA
[00:32]2018DOTA2亚洲邀请赛出场——LGD
2018/04/04 DOTA
使用Python编写提取日志中的中文的脚本的方法
2015/04/30 Python
Python实现比较扑克牌大小程序代码示例
2017/12/06 Python
python初学之用户登录的实现过程(实例讲解)
2017/12/23 Python
Python之多线程爬虫抓取网页图片的示例代码
2018/01/10 Python
详谈Python 窗体(tkinter)表格数据(Treeview)
2018/10/11 Python
Python面向对象程序设计类变量与成员变量、类方法与成员方法用法分析
2019/04/12 Python
scikit-learn线性回归,多元回归,多项式回归的实现
2019/08/29 Python
Django Admin中增加导出CSV功能过程解析
2019/09/04 Python
使用Python进行防病毒免杀解析
2019/12/13 Python
详解向scrapy中的spider传递参数的几种方法(2种)
2020/09/28 Python
详解canvas绘制多张图的排列顺序问题
2019/01/21 HTML / CSS
WWE美国职业摔角官方商店:WWE Shop
2018/11/15 全球购物
什么是.net
2015/08/03 面试题
无犯罪记录证明
2014/09/19 职场文书
用CSS3画一个爱心
2021/04/27 HTML / CSS