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处理VBArray的函数使用说明
May 11 Javascript
解决IE6的PNG透明JS插件使用介绍
Apr 17 Javascript
jquery无刷新验证邮箱地址实现实例
Feb 19 Javascript
javascript中Function类型详解
Apr 28 Javascript
基于React实现表单数据的添加和删除详解
Mar 14 Javascript
基于JavaScript实现数码时钟效果
Mar 30 Javascript
详谈commonjs模块与es6模块的区别
Oct 18 Javascript
基于vue开发的在线付费课程应用过程
Jan 25 Javascript
JS实现的倒计时恢复按钮点击功能【可用于协议阅读倒计时】
Apr 19 Javascript
jQuery AJAX 方法success()后台传来的4种数据详解
Aug 08 jQuery
vue 父组件通过$refs获取子组件的值和方法详解
Nov 07 Javascript
jQuery实现的解析本地 XML 文档操作示例
Apr 30 jQuery
探寻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
PHP中,文件上传
2006/12/06 PHP
php文章内容分页并生成相应的htm静态页面代码
2010/06/07 PHP
php生成excel文件的简单方法
2014/02/08 PHP
如何优雅的使用 laravel 的 validator验证方法
2018/11/11 PHP
javaScript call 函数的用法说明
2010/04/09 Javascript
javascript学习笔记(八) js内置对象
2012/06/19 Javascript
(跨浏览器基础事件/浏览器检测/判断浏览器)经验代码分享
2013/01/24 Javascript
去掉gridPanel表头全选框的小例子
2013/07/18 Javascript
JavaScript 和 Java 的区别浅析
2013/07/31 Javascript
实现checkbox全选、反选、取消JavaScript小脚本异常
2014/04/10 Javascript
JavaScript中变量声明有var和没var的区别示例介绍
2014/09/15 Javascript
JS小游戏之仙剑翻牌源码详解
2014/09/25 Javascript
jQuery实现购物车计算价格功能的方法
2015/03/25 Javascript
BootStrap文件上传样式超好看【持续更新】
2016/05/10 Javascript
JQuery validate插件验证用户注册信息
2016/05/11 Javascript
Js利用console计算代码运行时间的方法示例
2017/09/24 Javascript
node.js基于socket.io快速实现一个实时通讯应用
2019/04/23 Javascript
vue项目实现图片上传功能
2019/12/23 Javascript
Python 除法小技巧
2008/09/06 Python
Windows和Linux下使用Python访问SqlServer的方法介绍
2015/03/10 Python
Python实现文件复制删除
2016/04/19 Python
django开发之settings.py中变量的全局引用详解
2017/03/29 Python
pyspark.sql.DataFrame与pandas.DataFrame之间的相互转换实例
2018/08/02 Python
python的sorted用法详解
2019/06/25 Python
关于python3中setup.py小概念解析
2019/08/22 Python
python实现模拟器爬取抖音评论数据的示例代码
2021/01/06 Python
深入浅出CSS3 background-clip,background-origin和border-image教程
2011/01/27 HTML / CSS
canvas使用注意点总结
2013/07/19 HTML / CSS
澳大利亚小众服装品牌:Maurie & Eve
2018/03/27 全球购物
什么是虚拟内存?虚拟内存有什么优势?
2012/02/19 面试题
经济管理专业自荐信
2013/12/30 职场文书
装修设计师求职信
2014/02/26 职场文书
《永远的白衣战士》教学反思
2014/04/25 职场文书
导游词之山海关
2019/12/10 职场文书
利用Python第三方库实现预测NBA比赛结果
2021/06/21 Python
Python+Matplotlib+LaTeX玩转数学公式
2022/02/24 Python