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的load函数动态加载其它页面的内容的实现代码
Dec 14 Javascript
chrome原生方法之数组
Nov 30 Javascript
onclick与listeners的执行先后问题详细解剖
Jan 07 Javascript
提高jQuery性能的十个诀窍
Nov 14 Javascript
jQuery实现查找链接文字替换属性的方法
Jun 27 Javascript
浅谈js内置对象Math的属性和方法(推荐)
Sep 19 Javascript
JavaScript限定范围拖拽及自定义滚动条应用(3)
May 17 Javascript
详解webpack + vue + node 打造单页面(入门篇)
Sep 23 Javascript
javascript字体颜色控件的开发 JS实现字体控制
Nov 27 Javascript
解决vuejs项目里css引用背景图片不能显示的问题
Sep 13 Javascript
JavaScript学习笔记之数组基本操作示例
Jan 09 Javascript
uniapp微信小程序:key失效的解决方法
Jan 20 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 防止单引号,双引号在接受页面转义
2008/07/10 PHP
php中数组首字符过滤功能代码
2012/07/31 PHP
PHP临时文件的安全性分析
2014/07/04 PHP
yii2中的rules 自定义验证规则详解
2016/04/19 PHP
PHP实现自动发送邮件功能代码(qq 邮箱)
2017/08/18 PHP
PHP中使用jQuery+Ajax实现分页查询多功能操作(示例讲解)
2017/09/17 PHP
屏蔽鼠标右键、Ctrl+n、shift+F10、F5刷新、退格键 的javascript代码
2007/04/01 Javascript
基于jquery的让textarea自适应高度的插件
2010/08/03 Javascript
js arguments对象应用介绍
2012/11/28 Javascript
jquery右下角弹出提示框示例代码
2013/10/08 Javascript
arguments对象验证函数的参数是否合法
2015/06/26 Javascript
BootstrapTable与KnockoutJS相结合实现增删改查功能【二】
2016/05/10 Javascript
Javascript Event(事件)的传播与冒泡
2017/01/23 Javascript
js 公式编辑器 - 自定义匹配规则 - 带提示下拉框 - 动态获取光标像素坐标
2018/01/04 Javascript
Vue 中axios配置实例详解
2018/07/27 Javascript
使用mixins实现elementUI表单全局验证的解决方法
2019/04/02 Javascript
JavaScript常用内置对象用法分析
2019/07/09 Javascript
解决layui 三级联动下拉框更新时回显的问题
2019/09/03 Javascript
详解Typescript 内置的模块导入兼容方式
2020/05/31 Javascript
Python中Django 后台自定义表单控件
2017/03/28 Python
Python Flask基础教程示例代码
2018/02/07 Python
利用python库在局域网内传输文件的方法
2018/06/04 Python
Python基于机器学习方法实现的电影推荐系统实例详解
2019/06/25 Python
用python3读取python2的pickle数据方式
2019/12/25 Python
python反爬虫方法的优缺点分析
2020/11/25 Python
python之pygame模块实现飞机大战完整代码
2020/11/29 Python
蛋白质世界:Protein World
2017/11/23 全球购物
NBA欧洲商店(英国):NBA Europe Store UK
2018/07/27 全球购物
英国女性时尚鞋类的潮流制造者:Koi Footwear
2018/10/19 全球购物
Eclipse面试题
2014/03/22 面试题
护理工作感言
2014/01/16 职场文书
电气自动化专业职业规划范文
2014/02/16 职场文书
干部个人对照检查材料
2014/08/25 职场文书
党员批评与自我批评
2014/10/15 职场文书
白酒代理协议书范本
2014/10/26 职场文书
2015年纪委工作总结
2015/05/13 职场文书