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 事件对象的实现
Jul 13 Javascript
javascript中的107个基础知识收集整理 推荐
Mar 29 Javascript
jquery实现的动态回到顶部特效代码
Oct 28 Javascript
javascript中Date format(js日期格式化)方法小结
Dec 17 Javascript
Angularjs中使用轮播图指令swiper
May 30 Javascript
jQuery EasyUI 选项卡面板tabs的使用实例讲解
Dec 25 jQuery
Angular2之二级路由详解
Aug 31 Javascript
JavaScript原型对象原理与应用分析
Dec 27 Javascript
在微信小程序中使用图表的方法示例
Apr 25 Javascript
微信小程序自定义头部导航栏和导航栏背景图片 navigationStyle问题
Jul 26 Javascript
taro小程序添加骨架屏的实现代码
Nov 15 Javascript
js实现手表表盘时钟与圆周运动
Sep 18 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
点评山进PR-D3L三波段收音机
2021/03/02 无线电
在 PHP 中使用随机数的三个步骤
2006/10/09 PHP
windows xp下安装pear
2006/12/02 PHP
PHP添加MySQL数据记录代码
2008/06/07 PHP
mayfish 数据入库验证代码
2010/04/30 PHP
PHP常见漏洞攻击分析
2016/02/21 PHP
js 替换
2008/02/19 Javascript
使用jQuery模板来展现json数据的代码
2010/10/22 Javascript
jquery模拟按下回车实现代码
2011/09/20 Javascript
文档对象模型DOM通俗讲解
2013/11/01 Javascript
使用jQuery仿苹果官网焦点图特效
2014/12/23 Javascript
深入理解JavaScript系列(37):设计模式之享元模式详解
2015/03/04 Javascript
简易的投票系统以及js刷票思路和方法
2015/04/07 Javascript
javascript实现Table间隔色以及选择高亮(和动态切换数据)的方法
2015/05/14 Javascript
解决Vue2.0自带浏览器里无法打开的原因(兼容处理)
2017/07/28 Javascript
详解Vue用自定义指令完成一个下拉菜单(select组件)
2017/10/31 Javascript
Node解决简单重复问题系列之Excel内容的获取
2018/01/02 Javascript
微信小程序动态增加按钮组件
2018/09/14 Javascript
VUE基于NUXT的SSR 服务端渲染
2018/11/30 Javascript
node crawler如何添加promise支持
2020/02/01 Javascript
vue项目实现多语言切换的思路
2020/09/17 Javascript
python使用beautifulsoup从爱奇艺网抓取视频播放
2014/01/23 Python
Python 遍历列表里面序号和值的方法(三种)
2017/02/17 Python
PyTorch 普通卷积和空洞卷积实例
2020/01/07 Python
CSS3中Animation动画属性用法详解
2016/07/04 HTML / CSS
墨尔本最受欢迎的复古风格品牌:Princess Highway
2018/12/21 全球购物
J2EE中常用的名词进行解释
2015/11/09 面试题
生物制药毕业生自荐信
2013/10/16 职场文书
11月红领巾广播稿
2014/01/17 职场文书
酒店拾金不昧表扬信
2014/01/18 职场文书
无刑事犯罪记录证明范本
2014/09/29 职场文书
面试通知邮件
2015/04/20 职场文书
党小组评议意见
2015/06/02 职场文书
永不妥协观后感
2015/06/10 职场文书
Python jiaba库的使用详解
2021/11/23 Python
Java由浅入深通关抽象类与接口(上篇)
2022/04/26 Java/Android