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 相关文章推荐
当jQuery遭遇CoffeeScript的时候 使用分享
Sep 17 Javascript
JavaScript 判断浏览器是否支持SVG的代码
Mar 21 Javascript
使用jQuery jqPlot插件绘制柱状图
Dec 18 Javascript
AngularJS入门教程之ng-class 指令用法
Aug 01 Javascript
JS实现的样式切换功能tableCSS实例
Dec 30 Javascript
解决给dom元素绑定click等事件无效问题的方法
Feb 17 Javascript
原生js实现选项卡功能
Mar 08 Javascript
Angularjs 动态添加指令并绑定事件的方法
Apr 13 Javascript
ES6入门教程之Array.from()方法
Mar 23 Javascript
Node.js fs模块(文件模块)创建、删除目录(文件)读取写入文件流的方法
Sep 03 Javascript
js实现窗口全屏示例详解
Sep 17 Javascript
html2canvas属性和使用方法以及如何使用html2canvas将HTML内容写入Canvas生成图片
Jan 12 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
snoopy PHP版的网络客户端提供本地下载
2008/04/15 PHP
php下将XML转换为数组
2010/01/01 PHP
php中去除所有js,html,css代码
2010/10/12 PHP
PHP验证码函数代码(简单实用)
2013/09/29 PHP
php使用str_replace实现输入框回车替换br的方法
2014/11/24 PHP
如何使用纯PHP实现定时器任务(Timer)
2015/07/31 PHP
CI框架(ajax分页,全选,反选,不选,批量删除)完整代码详解
2016/11/01 PHP
PHP7 错误处理机制修改
2021/03/09 PHP
js宝典学习笔记(上)
2007/01/10 Javascript
JavaScript Archive Network 集合
2007/05/12 Javascript
自己整理的一个javascript日期处理函数
2010/10/16 Javascript
基于jquery的文本框与autocomplete结合使用(asp.net+json)
2012/05/30 Javascript
JavaScript String(字符串)对象的简单实例(推荐)
2016/08/31 Javascript
微信小程序网络请求封装示例
2018/07/24 Javascript
layui的table单击行勾选checkbox功能方法
2018/08/14 Javascript
vue实现页面滚动到底部刷新
2019/08/16 Javascript
js实现随机div颜色位置 类似满天星效果
2019/10/24 Javascript
vue商城中商品“筛选器”功能的实现代码
2020/07/01 Javascript
Vue登录拦截 登录后继续跳转指定页面的操作
2020/08/04 Javascript
[06:07]DOTA2-DPC中国联赛 正赛 Ehome vs VG 选手采访
2021/03/11 DOTA
跟老齐学Python之使用Python操作数据库(1)
2014/11/25 Python
python+opencv实现的简单人脸识别代码示例
2017/11/14 Python
Python工程师面试必备25条知识点
2018/01/17 Python
Python 3.8新特征之asyncio REPL
2019/05/28 Python
Python 跨.py文件调用自定义函数说明
2020/06/01 Python
keras的siamese(孪生网络)实现案例
2020/06/12 Python
AE美国鹰美国官方网站:American Eagle Outfitters
2016/08/22 全球购物
三星印度官网:Samsung印度
2019/08/03 全球购物
军训生自我鉴定范文
2013/12/27 职场文书
财务主管的岗位职责
2013/12/30 职场文书
教师辞职报告范文
2014/01/20 职场文书
担保书格式及范文
2014/04/01 职场文书
班主任工作经验交流材料
2014/05/13 职场文书
全国税务系统先进集体事迹材料
2014/05/19 职场文书
2019交通安全宣传标语集锦!
2019/06/28 职场文书
遇事可以测出您的见识与格局
2019/09/16 职场文书