javascript学习指南之回调问题


Posted in Javascript onApril 23, 2016

回调地狱

对 JavaScript 程序员来说,处理回调是家常,但是处理层次过深的回调就没有那么美好了,下面的示例代码片段用了三层回调,再补脑一下更多层的场景,简直是酸爽,这就是传说中的回调地狱。

getDirectories(function(dirs) {
  getFiles(dirs[0], function(files) {
    getContent(files[0], function(file, content) {
      console.log('filename:', file);
      console.log(content);
    });
  });
});
 
function getDirectories(callback) {
 setTimeout(function() {
  callback(['/home/ben']);
 }, 1000);
}
 
function getFiles(dir, callback) {
  setTimeout(function() {
    callback([dir + '/test1.txt', dir + '/test2.txt']);
  }, 1000)
}
 
function getContent(file, callback) {
  setTimeout(function() {
    callback(file, 'content');
  }, 1000)
}

解决方案

生态圈中有很多异步解决方案可以处理回调地狱的问题,比如 bluebird、Q 等,本文重点介绍 ECMAScript 6/7 规范中对异步编程的支持。

ES6 Promise

Promise 是一种异步编程的解决方案,是解决回调地狱问题的利器。

Promise 在 JavaScript 生态圈被主流接受是在 2007 年 Dojo 框架增加了 dojo.Deferred 的功能。随着 dojo.Deferred 的流行,在 2009 年 Kris Zyp 提出了 CommonJS Promises/A 规范。随后生态圈中出现了大量 Promise 实现包括 Q.js、FuturesJS 等。当然 Promise 之所有这么流行很大程度上是由于 jQuery 的存在,只是 jQuery 并不完全遵守 CommonJS Promises/A 规范。随后正如大家看到的,ES 6 规范包含了 Promise。
MDN 中对 Promise 是这样描述的:

Promise 对象是一个返回值的代理,这个返回值在promise对象创建时未必已知。它允许你为异步操作的成功或失败指定处理方法。 这使得异步方法可以像同步方法那样返回值:异步方法会返回一个包含了原返回值的
以下的代码是「回调地狱」一节中的示例通过 Promise 实现,看上去代码也不是很简洁,但是比起传统的层级回调有明显改善,代码可维护性和可读性更强。

getDirectories().then(function(dirs) {
  return getFiles(dirs[0]);
}).then(function(files) {
  return getContent(files[0]);
}).then(function(val) {
  console.log('filename:', val.file);
  console.log(val.content);
});
 
function getDirectories() {
  return new Promise(function (resolve, reject) {
    setTimeout(function() {
    resolve(['/home/ben']);
   }, 1000);
  });
}
 
function getFiles(dir) {
  return new Promise(function (resolve, reject) {
    setTimeout(function() {
      resolve([dir + '/test1.txt', dir + '/test2.txt']);
    }, 1000);
  });
}
 
function getContent(file) {
  return new Promise(function (resolve, reject) {
    setTimeout(function() {
      resolve({file: file, content: 'content'});
    }, 1000);
  });
}

ES6 Generator

Promise 的实现方式还不够简洁,我们还需要更好的选择,co 就是选择之一。co 是基于 Generator(生成器)的异步流控制器,了解 co 之前首先需要理解 Generator。熟悉 C# 的同学应该都有了解,C# 2.0 的版本就引入了 yield 关键字,用于迭代生成器。ES 6 Generator 跟 C# 相似,也使用了 yield 语法糖,内部实现了状态机。具体用法可以参考 MDN 的文档 function* 一节,原理可以参考AlloyTeam 团队 Blog 深入理解 Generator。使用 co 巧妙结合 ES6 Generator 和 ES6 Promise 让异步调用更加和谐。

co(function* (){
  var dirs = yield getDirectories();
  var files = yield getFiles(dirs[0]);
  var contentVal = yield getContent(files[0]);
  console.log('filename:', contentVal.file);
  console.log(contentVal.content);
});

co 非常巧妙,其核心代码可以简化如下的示例,大体思路是采用递归遍历生成器直到状态完成,当然 co 做的跟多。

runGenerator();
 
function* run(){
  var dirs = yield getDirectories();
  var files = yield getFiles(dirs[0]);
  var contentVal = yield getContent(files[0]);
  console.log('filename:', contentVal.file);
  console.log(contentVal.content);
}
 
function runGenerator(){
  var gen = run();
 
  function go(result){
    if(result.done) return;
    result.value.then(function(r){
      go(gen.next(r));
    });
  }
 
  go(gen.next());
}

ES7 Async/Await

ES6 Generator 确实很好,只可惜需要第三方库的支持。好消息是 ES 7 会引入 Async/Await 关键字完美解决异步调用的问题。好吧,.net 又领先了一步,.net framework 4.5 已经率先支持了。
今后的代码写起来是这样:

run();
async function run() {
  var dirs = await getDirectories();
  var files = await getFiles(dirs[0]);
  var contentVal = await getContent(files[0]);
  console.log('filename:', contentVal.file);
  console.log(contentVal.content);
}

结论

从经典的回调的异步编程方式,到 ES6 Promise 规范对异步编程的改善,再到 co 结合 ES Generator 优雅处理,最后 ES7 async/await 完美收官,可以让我们了解为什么 ECMAScript 会出现这些特性以及解决了什么问题,更加清晰地看到 JavaScript 异步编程发展的脉络。

Javascript 相关文章推荐
鼠标焦点离开文本框时验证的js代码
Jul 19 Javascript
jQuery实现点击按钮弹出可关闭层的浮动层插件
Sep 19 Javascript
jquery实现简单实用的弹出层效果代码
Oct 15 Javascript
基于jquery实现鼠标滚轮驱动的图片切换效果
Oct 26 Javascript
你不知道的高性能JAVASCRIPT
Jan 18 Javascript
js学习阶段总结(必看篇)
Jun 16 Javascript
jQuery 选择符详细介绍及整理
Dec 02 Javascript
jQuery插件echarts实现的单折线图效果示例【附demo源码下载】
Mar 04 Javascript
jQuery插件Echarts实现的渐变色柱状图
Mar 23 jQuery
JS触摸与手势事件详解
May 09 Javascript
ajax前台后台跨域请求处理方式
Feb 08 Javascript
JS实现瀑布流效果
Mar 07 Javascript
探寻JavaScript中this指针指向
Apr 23 #Javascript
javascript中this指向详解
Apr 23 #Javascript
JavaScript实现Base64编码转换
Apr 23 #Javascript
jQuery UI库中dialog对话框功能使用全解析
Apr 23 #Javascript
详解jQuery UI库中文本输入自动补全功能的用法
Apr 23 #Javascript
AngularJS中的过滤器filter用法完全解析
Apr 22 #Javascript
举例讲解如何判断JavaScript中对象的类型
Apr 22 #Javascript
You might like
抓取YAHOO股票报价的类
2009/05/15 PHP
php 模拟POST提交的2种方法详解
2013/06/17 PHP
php function用法如何递归及return和echo区别
2014/03/07 PHP
php微信开发之谷歌测距
2018/06/14 PHP
EXTJS内使用ACTIVEX控件引起崩溃问题的解决方法
2010/03/31 Javascript
jQuery选择器的工作原理和优化分析
2011/07/25 Javascript
js验证身份证号有效性并提示对应信息
2015/10/19 Javascript
极易被忽视的javascript面试题七问七答
2016/02/15 Javascript
javascript url几种编码方式详解
2016/06/06 Javascript
Zabbix添加Node.js监控的方法
2016/10/20 Javascript
vuejs使用axios异步访问时用get和post的实例讲解
2018/08/09 Javascript
node.js express框架简介与实现
2019/07/23 Javascript
通过vue写一个瀑布流插件代码实例
2019/09/07 Javascript
vue仿ios列表左划删除
2019/09/26 Javascript
vue实现购物车结算功能
2020/06/18 Javascript
关于vue 结合原生js 解决echarts resize问题
2020/07/26 Javascript
v-slot和slot、slot-scope之间相互替换实例
2020/09/04 Javascript
微信小程序input抖动问题的修复方法
2021/03/03 Javascript
[46:16]2018DOTA2亚洲邀请赛3月30日 小组赛B组 iG VS VP
2018/03/31 DOTA
[48:27]EG vs Liquid 2018国际邀请赛淘汰赛BO3 第二场 8.25
2018/08/29 DOTA
windows10系统中安装python3.x+scrapy教程
2016/11/08 Python
python使用代理ip访问网站的实例
2018/05/07 Python
解决python super()调用多重继承函数的问题
2019/06/26 Python
python中pip的使用和修改下载源的方法
2019/07/08 Python
PyTorch学习:动态图和静态图的例子
2020/01/06 Python
Python环境搭建过程从安装到Hello World
2021/02/05 Python
CSS3中的opacity属性使用教程
2015/08/19 HTML / CSS
CSS3 真的会替代 SCSS 吗
2021/03/09 HTML / CSS
HTML5+CSS3模仿优酷视频截图功能示例
2017/01/05 HTML / CSS
HTML5的Video标签有部分MP4无法播放的问题解析(多图)
2017/08/18 HTML / CSS
幼儿教师师德师风演讲稿
2014/08/22 职场文书
本科毕业论文致谢词
2015/05/14 职场文书
如何书写公司员工保密协议?
2019/06/27 职场文书
中国现代文学之经典散文三篇
2019/09/18 职场文书
Pandas实现DataFrame的简单运算、统计与排序
2022/03/31 Python
python实现双向链表原理
2022/05/25 Python