javascript学习指南之回调问题

回调地狱

对 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 异步编程发展的脉络。

(0)

相关推荐

  • 使用 stylelint检查CSS_StyleLint

    当你书写大量的CSS代码时,可能会出现不止一个的错误.可能需要某个工具来阻止你CSS书写的错误. 可能,有的时候你的错误真的是一个bug.也有可能仅仅因为草率造成的不一致或者不明确的代码风格.可能它们当中的许多看起来微不足道(取决于你的性子),但是随着代码库的增多以及时间累积,许多人使用时就会做出有丑陋的东西.事情的后果不是你可以想象的. 你尝试去控制自己.你的同事也帮助你,当你游离及时纠正你的错误.但是,你和你的同事都是错误的制造者,所以最后至少在一定程度上都不可避免的失败了.后来,你或者其他

  • 深入浅析JavaScript中数据共享和数据传递

    数据共享和数据传递是相辅相成的,我们一起来讨论这个问题.首先要说的是共享和传递都是有作用域的.作用域就是起作用的区域,在同一个作用域数据可以共享,超过这个作用域就是跨作用域,就得用到数据传递了. 作用域 1.ui作用域 每一个ui文件缺省都有对应的ui.js.他们作为一个闭合的作用域.ui.js里根据ui文件里组件的id来获取ui对象;不同的ui文件可以定义相同id的组件.在ui.js里定义的变量只能在这个js里访问. 2.page作用域 每次调用openPage都会打开一个新的page,这个新

  • 下雪了 javascript实现雪花飞舞

    本文实例为大家分享了雪花飞舞效果javascript实现,供大家参考,具体内容如下 原理: 1.js动态创建DIV,指定CLASS类设置不同的背景图样式显示不同的雪花效果. 2.js获取创建的DIV并改变其top属性值,当下落的高度大于屏幕高后删除该移动div 3.好像不够完善勿喷 效果预览:http://wjf444128852.github.io/demo02/snow/index.html HTML代码: <!DOCTYPE html> <html lang="en&qu

  • JavaScript常用字符串与数组扩展函数小结

    String对象的扩展函数: String.prototype.trim = function() { return this.replace(/^\s+|\s+$/g,""); } String.prototype.ltrim = function() { return this.replace(/^\s+/g,""); } String.prototype.rtrim = function() { return this.replace(/\s+$/g,&quo

  • javascript学习指南之回调问题

    回调地狱 对 JavaScript 程序员来说,处理回调是家常,但是处理层次过深的回调就没有那么美好了,下面的示例代码片段用了三层回调,再补脑一下更多层的场景,简直是酸爽,这就是传说中的回调地狱. getDirectories(function(dirs) { getFiles(dirs[0], function(files) { getContent(files[0], function(file, content) { console.log('filename:', file); cons

  • Javascript学习指南

    javascript入门太容易了,导致几乎人人随便看看就能上手,零基础的人学个三五天都能对外宣称自己掌握了js.可是真正掌握js是一件很难的事情.如果在初学一门语言的时候第一想到的是问别人,是很难取得进步的.因为得到答案太容易,而不会去想为什么.而且说实话,js并不适合作为第一门编程语言,它兼容并包,容错性高,但这也意味着一但出错你就很难找到错误的原因.另一方面js有一些优秀的特性对于没有编程经历的人来说也很难发现. 如果你真想学好js,我有个大概的自学轨迹供你们参考. 第一步,学习w3scho

  • 函数式JavaScript编程指南

    简介 你是否知道JavaScript其实也是一个函数式编程语言呢?本指南将教你如何利用JavaScript的函数式特性. 要求:你应当已经对JavaScript和DOM有了一个基本的了解. 写这篇指南的目的是因为关于JavaScript编程的资料太多了但是极少的资料提到了JavaScript的函数式特性.在本指南中,我只会讲解这些基本知识而不会深入其它的函数式语言或这是Lambda算子. 你可以点击所有的例子然后你所看到的代码就会被执行,这样就可以令指南变得具有交互性.你也可以使用这个沙箱来尝试

  • JavaScript学习笔记之ES6数组方法

    ES6(ECMAScript 6)是即将到来的新版本JavaScript语言的标准,代号harmony(和谐之意,显然没有跟上我国的步伐,我们已经进入中国梦版本了).上一次标准的制订还是2009年出台的ES5.目前ES6的标准化工作正在进行中,预计会在14年12月份放出正式敲定的版本.但大部分标准已经就绪,且各浏览器对ES6的支持也正在实现中. ES6给数组添加了一些新特性,而这些新特性到目前为止完全可以运用到自己的业务层.在这一节中将总结有关于ES6给数组提供一些新特性的使用方法. ES6提供

  • JavaScript学习笔记整理_setTimeout的应用

    setTimeou的t应用 var ids = []; function foo1(i) { this.i = i; console.log('i = '+i); ids[0] = setTimeout((function () { foo1(i); }),1000); } function foo2(j) { this.j = j; console.log('j = '+j); ids[1] = setTimeout((function () { foo2(j); }),1000); } fo

  • JavaScript学习笔记之数组求和方法

    推荐阅读:JavaScript学习笔记之数组的增.删.改.查 通过最近学习,越来越感觉JavaScript的强大与魅力之处,仅数组一个知识点就让我这样的初学者折腾了很久.折腾是折腾,但还是很有效果的,这几天的学习到的一些基本知识,就在自己的业务就用到了.顿感自信心爆棚,我也有一天能学会JavaScript. 别的不多说了,咱们今天一起来玩数组的求和,看看有哪些方法可以实现数组的求和. 数组方法 JavaScript中数组有很多种方法,下图能更好的向你阐述JavaScript有多少种方法,作用是啥

  • JavaScript学习笔记之取数组中最大值和最小值

    推荐阅读:JavaScript学习笔记之数组的增.删.改.查 JavaScript学习笔记之数组求和方法 JavaScript学习笔记之数组随机排序 在实际业务中有的时候要取出数组中的最大值或最小值.但在数组中并没有提供arr.max()和arr.min()这样的方法.那么是不是可以通过别的方式实现类似这样的方法呢?那么今天我们就来整理取出数组中最大值和最小值的一些方法. 取数组中最大值 可以先把思路理一下: 将数组中第一个元素赋值给一个变量,并且把这个变量作为最大值: 开始遍历数组,从第二个元

  • javascript学习笔记(四)function函数部分

    函数是由事件驱动的或者当它被调用时执行的可重复使用的代码块. Jscript 支持两种函数:一类是语言内部的函数(如eval() ),另一类是自己创建的. 在 JavaScript 函数内部声明的变量(使用 var)是局部变量,所以只能在函数内部访问它.(该变量的作用域是局部的). 您可以在不同的函数中使用名称相同的局部变量,因为只有声明过该变量的函数才能识别出该变量. 函数的调用方式 1.普通调用:functionName(实际参数...) 2.通过指向函数的变量去调用: var  myVar

  • 详解阿里Node.js技术文档之process模块学习指南

    模块概览 process是node的全局模块,作用比较直观.可以通过它来获得node进程相关的信息,比如运行node程序时的命令行参数.或者设置进程相关信息,比如设置环境变量. 环境变量:process.env 使用频率很高,node服务运行时,时常会判断当前服务运行的环境,如下所示 if(process.env.NODE_ENV === 'production'){ console.log('生产环境'); }else{ console.log('非生产环境'); } 运行命令 NODE_EN

  • JavaScript中异步与回调的基本概念及回调地狱现象

    目录 JavaScript异步与回调 一.前言 二.异步函数 三.回调函数 四.回调的回调 五.回调地狱 六.总结 JavaScript异步与回调 一.前言 在学习本文内容之前,我们必须要先了解异步的概念,首先要强调的是异步和并行有着本质的区别. 并行,一般指并行计算,是说同一时刻有多条指令同时被执行,这些指令可能执行于同一CPU的多核上,或者多个CPU上,或者多个物理主机甚至多个网络中. 同步,一般指按照预定的顺序依次执行任务,只有当上一个任务完成后,才开始执行下一个任务. 异步,与同步相对应

随机推荐