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 相关文章推荐
php对mongodb的扩展(小试牛刀)
Nov 11 Javascript
js判读浏览器是否支持html5的canvas的代码
Nov 18 Javascript
删除条目时弹出的确认对话框
Jun 05 Javascript
js带缩略图的图片轮播效果代码分享
Sep 14 Javascript
轻松学习Javascript闭包函数
Dec 15 Javascript
js实现页面a向页面b传参的方法
May 29 Javascript
jQuery实现最简单实用的分秒倒计时
Feb 05 Javascript
详解vue axios二次封装
Jul 22 Javascript
利用es6 new.target来对模拟抽象类的方法
May 10 Javascript
微信小程序自定义底部弹出框功能
Nov 18 Javascript
Vue vee-validate插件的简单使用
Jun 22 Vue.js
vue项目配置sass及引入外部scss文件
Apr 14 Vue.js
探寻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
apache rewrite_module模块使用教程
2008/01/10 PHP
PHP教程 预定义变量
2009/10/23 PHP
SWFUpload与CI不能正确上传识别文件MIME类型解决方法分享
2011/04/18 PHP
百度站点地图(百度sitemap)生成方法分享
2014/01/09 PHP
ThinkPHP框架任意代码执行漏洞的利用及其修复方法
2014/07/04 PHP
PHP7正式版测试,性能惊艳!
2015/12/08 PHP
php和C#的yield迭代器实现方法对比分析
2019/07/17 PHP
jQuery 表单验证插件formValidation实现个性化错误提示
2009/06/23 Javascript
JavaScript 获取当前时间戳的代码
2010/08/05 Javascript
javascript拖拽应用实例
2016/03/25 Javascript
Bootstrop实现多级下拉菜单功能
2016/11/24 Javascript
JS正则截取两个字符串之间及字符串前后内容的方法
2017/01/06 Javascript
JavaScript调试的多个必备小Tips
2017/01/15 Javascript
原生js实现鼠标跟随效果
2017/02/28 Javascript
js简单实现网页换肤功能
2017/04/07 Javascript
VUE Error: getaddrinfo ENOTFOUND localhost
2018/05/03 Javascript
详解解决小程序中webview页面多层history返回问题
2019/08/20 Javascript
Vue基础配置讲解
2019/11/29 Javascript
js实现限定范围拖拽的示例
2020/10/26 Javascript
[06:25]第二届DOTA2亚洲邀请赛主赛事第二天比赛集锦.mp4
2017/04/03 DOTA
[36:33]完美世界DOTA2联赛PWL S2 LBZS vs Forest 第二场 11.29
2020/12/02 DOTA
使用Python的Scrapy框架十分钟爬取美女图
2016/12/26 Python
解决python中os.listdir()函数读取文件夹下文件的乱序和排序问题
2018/10/17 Python
Django工程的分层结构详解
2019/07/18 Python
HTML5移动端手机网站开发流程
2016/04/25 HTML / CSS
Original Penguin英国官方网站:美国著名休闲时装品牌
2016/10/30 全球购物
俄罗斯儿童和青少年服装、鞋子及配件的在线商店:Orby
2020/02/20 全球购物
利用promise及参数解构封装ajax请求的方法
2021/03/24 Javascript
工厂厂长岗位职责
2013/11/08 职场文书
市场营销战略计划书
2014/05/06 职场文书
俄语专业毕业生求职信
2014/07/12 职场文书
音乐学专业求职信
2014/07/22 职场文书
法人委托书的范本格式
2014/09/11 职场文书
医学检验专业自荐信
2014/09/18 职场文书
导游词之镜泊湖
2019/12/09 职场文书
Linux中sftp常用命令整理
2022/06/28 Servers