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 相关文章推荐
javascript之更有效率的字符串替换
Aug 02 Javascript
让iframe子窗体取父窗体地址栏参数(querystring)
Oct 13 Javascript
js文件包含的几种方式介绍
Sep 28 Javascript
用js代码和插件实现wordpress雪花飘落效果的四种方法
Dec 15 Javascript
JS动画效果打开、关闭层的实现方法
May 09 Javascript
jQuery基础的工厂函数以及定时器的经典实例分析
May 20 Javascript
Node.JS更改Windows注册表Regedit的方法小结
Aug 18 Javascript
vue+node+webpack环境搭建教程
Nov 05 Javascript
用vue写一个仿简书的轮播图的示例代码
Mar 13 Javascript
Vue动态控制input的disabled属性的方法
Jun 26 Javascript
vue 中几种传值方法(3种)
Nov 12 Javascript
vue.js使用v-model实现父子组件间的双向通信示例
Feb 05 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
日本十大最佳动漫,全都是二次元的神级作品
2019/10/05 日漫
生成静态页面的PHP类
2006/11/25 PHP
ThinkPHP3.0略缩图不能保存到子目录的解决方法
2012/09/30 PHP
destoon在360浏览器下出现用户被强行注销的解决方法
2014/06/26 PHP
PHP中使用localhost连接Mysql不成功的解决方法
2014/08/20 PHP
简单介绍win7下搭建apache+php+mysql开发环境
2015/08/06 PHP
Yii2第三方类库插件Imagine的安装和使用
2017/07/06 PHP
javascript之更有效率的字符串替换
2008/08/02 Javascript
JavaScript 学习笔记之语句
2015/01/14 Javascript
Javascript BOM学习小结(六)
2015/11/26 Javascript
JavaScript中有关一个数组中最大值和最小值及它们的下表的输出的解决办法
2016/07/01 Javascript
基于jQuery实现Accordion手风琴自定义插件
2020/10/13 Javascript
微信小程序 flex实现导航实例详解
2017/04/26 Javascript
vue 右键菜单插件 简单、可扩展、样式自定义的右键菜单
2018/11/29 Javascript
了解JavaScript表单操作和表单域
2019/05/27 Javascript
详解vue beforeEach 死循环问题解决方法
2020/02/25 Javascript
js实现从右往左匀速显示图片(无缝轮播)
2020/06/29 Javascript
15个简单的JS编码标准让你的代码更整洁(小结)
2020/07/16 Javascript
JavaScript实现拖动对话框效果的实现代码
2020/10/12 Javascript
python实现socket端口重定向示例
2014/02/10 Python
python通过自定义isnumber函数判断字符串是否为数字的方法
2015/04/23 Python
编写Python CGI脚本的教程
2015/06/29 Python
python flask 多对多表查询功能
2017/06/25 Python
代码详解django中数据库设置
2019/01/28 Python
python 自动轨迹绘制的实例代码
2019/07/05 Python
python multiprocessing模块用法及原理介绍
2019/08/20 Python
Python响应对象text属性乱码解决方案
2020/03/31 Python
python多进程使用函数封装实例
2020/05/02 Python
python下载的库包存放路径
2020/07/27 Python
瑞士领先的网上超市:LeShop.ch
2018/11/14 全球购物
求职信的最佳写作思路
2014/02/01 职场文书
文明美德伴我成长演讲稿
2014/05/12 职场文书
分公司总经理岗位职责
2014/07/30 职场文书
行政专员岗位职责说明书
2014/09/01 职场文书
Python基础之Socket通信原理
2021/04/22 Python
MySQL 8.0 Online DDL快速加列的相关总结
2021/06/02 MySQL