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 相关文章推荐
让GoogleCode的SVN下的HTML文件在FireFox下正常显示.
May 25 Javascript
jQuery Deferred和Promise创建响应式应用程序详细介绍
Mar 05 Javascript
如何用js控制frame的隐藏或显示的解决办法
Mar 20 Javascript
JS控制阿拉伯数字转为中文大写示例代码
Sep 04 Javascript
手机平板等移动端适配跳转URL的js代码
Jan 25 Javascript
Thinkphp模板没有解析直接原样输出的解决方法
Oct 31 Javascript
JavaScript Function函数类型介绍
Apr 08 Javascript
jquery中的常见问题及快速解决方法小结
Jun 14 Javascript
实现React单页应用的方法详解
Aug 02 Javascript
jquery实现ajax提交表单信息的简单方法(推荐)
Aug 24 Javascript
微信小程序js文件改变参数并在视图上及时更新【推荐】
Jun 11 Javascript
微信小程序 textarea 层级过高问题简单解决方案
Oct 14 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
PHP扩展模块Pecl、Pear以及Perl的区别
2014/04/09 PHP
微信公众平台接口开发入门示例
2014/12/24 PHP
如何把php5.3版本升级到php5.4或者php5.5
2015/07/31 PHP
Joomla开启SEF的方法
2016/05/04 PHP
php 三大特点:封装,继承,多态
2017/02/19 PHP
Laravel Reponse响应客户端示例详解
2020/09/03 PHP
Javascript 读后台cookie代码
2008/09/15 Javascript
JS获取dom 对象 ajax操作 读写cookie函数
2009/11/18 Javascript
锋利的jQuery 要点归纳(一) jQuery选择器
2010/03/21 Javascript
js中AppendChild与insertBefore的用法详细解析
2013/12/16 Javascript
Egret引擎开发指南之创建项目
2014/09/03 Javascript
基于JS2Image实现圣诞树代码
2015/12/24 Javascript
让浏览器崩溃的12行JS代码(DoS攻击分析及防御)
2016/10/10 Javascript
webpack external模块的具体使用
2018/03/10 Javascript
js replace 全局替换的操作方法
2018/06/12 Javascript
Angular异步变同步处理方法
2018/08/13 Javascript
微信小程序实现通过双向滑动缩放图片大小的方法
2018/12/30 Javascript
vue组件中节流函数的失效的原因和解决方法
2020/12/02 Vue.js
闭包在python中的应用之translate和maketrans用法详解
2014/08/27 Python
Python判断文件和文件夹是否存在的方法
2015/05/21 Python
Ubuntu16.04/树莓派Python3+opencv配置教程(分享)
2018/04/02 Python
opencv调整图像亮度对比度的示例代码
2019/09/27 Python
树莓派升级python的具体步骤
2020/07/05 Python
Python Celery异步任务队列使用方法解析
2020/08/10 Python
使用索引(Index)有哪些需要考虑的因素
2016/10/19 面试题
文秘专业个人求职信
2013/12/22 职场文书
驾驶员岗位职责
2014/01/29 职场文书
幼儿教师国培感言
2014/02/19 职场文书
房地产项目建议书
2014/03/12 职场文书
班级读书活动总结
2014/06/30 职场文书
党员个人查摆剖析材料
2014/10/16 职场文书
安全教育培训心得体会
2016/01/15 职场文书
教你怎么用Python监控愉客行车程
2021/04/29 Python
php7中停止php-fpm服务的方法详解
2021/05/09 PHP
Python中glob库实现文件名的匹配
2021/06/18 Python
联想win10摄像头打不开怎么办?win10笔记本摄像头打不开解决办法
2022/04/08 数码科技