上一节讲异步原理的时候基本上把回掉函数也捎带讲了一些,这节主要举几个例子来具体化一下。在开始之前,首先要明白一件事,在javascript里函数可以作为参数进行传递,这里涉及到高阶函数的概念,大家可以自行google一下。

 
传统的同步函数需要返回一个结果的话都是通过return语句实现,例如:
 
function foo() {
var a = 3,
b = 2;
return a+b;
} var c = foo();
console.log(c); //
 
就是说后面的代码console.log要得到函数foo的运行结果只要调用该函数就可以得到它所返回的值a+b。
 
但是如果foo是一个异步函数,可以这样做吗?
 
异步函数的定义:
 
首先说一下javascript里面怎样书写异步函数。基本的方法就是,在你的函数定义里面调用别人已经提供的异步api (不管是原生的还是第三方的),你的函数也就是个异步函数了:
 
function foo(callback) {
你自己的代码;
asyncFn(function() {
var result = 你自己的代码;
callback(result);
});
}
上面这个例子中,你要定义一个函数foo,里面调用了一个异步函数asyncFn,在asyncFn运行完了之后调用foo的回调函数callback,来对结果result进行处理。
 
上面是一般异步函数的定义格式。setTimeout是javascript里面经常用的异步api。 做试验的时候经常用它来模拟异步操作,下面的例子模拟一个操作要运行1秒后才返回结果 (当然你可以设0秒,但仍然是异步的):
 
function foo(callback) {
你自己的代码;
setTimeout(function() {
var result = 你自己的代码;
callback(result);
}, 1000);
}
在node.js里面提供了其他的api来起到类似的作用(把你的同步代码写成异步函数),setImmediate或者process.nextTick,其作用就是异步调用你的代码。这两个基本上用法类似,但特定情况下是不同的,可以上网自行查找setImmediate,setTimeout(fn, 0)和process.nextTick 的区别,一般在不了解的情况下,建议使用setImmediate.
 
function foo(callback) {
你自己的代码;
setImmediate(function() {
var result = 你自己的代码;
callback(result);
});
} function foo(callback) {
你自己的代码;
process.nextTick(function() {
var result = 你自己的代码;
callback(result);
});
}

异步函数的调用:

以上是异步函数的定义,下面讲一下异步函数是怎样调用的。在调用异步函数的时候,回调函数有两种写法,一是直接写个匿名回调函数,下面例子中function(data) {...} 就是匿名回掉函数:
 
foo(function(data) {
你的代码来使用传回来的data;
});
 
二是先定义一个函数,然后使用函数引用作为回调:
 
function bar(data) {
你的代码来使用传回来的data;
} foo(bar);
现在回到开篇的问题,有同学一定看明白了,为什么异步函数不能用return来返回值。 下面把两种写法放一起,方便比较:
 
正确写法:
function foo(callback) {
你自己的代码;
asyncFn(function() {
var result = 你自己的代码;
callback(result);
});
}
错误写法:
function bar() {
你自己的代码;
asyncFn(function() {
var result = 你自己的代码;
}); return result;
}
 
在错误写法中,bar企图使用return来返回result。在上一篇已经讲过,异步api不会等执行完了再往下执行,也就是说asyncFn在调用后马上会往下执行return result这句,这时候asyncFn还在异步执行当中,result根本还没有计算出来,所以不能return期望结果。当然对javascript语法比较熟的同学也清楚,函数外部不能访问函数内部变量,就是说在asyncFn函数的外部是无法访问到result这个变量的,不管那个是主要原因,哪个是次要,异步api是肯定不能用return来返回值。
 
以上就是基本的异步函数的定义和调用。
 
异步函数实例:
 
任何只讲理论不讲应用的教程都是耍流氓。。。好吧,举个实际的例子:
 
网络请求是node.js异步API中常用的一种,能够进行网络请求的node.js原生和第三方的api非常多,下面以superagent为例来演示一下(运行之前先用npm安装superagent):
 
var agent = require('superagent');

agent.get('http://www.baidu.com')
.end(function(err, res) {
if(err) {
console.log(err);
} else {
console.log('http status: ' + res.status);
console.log(res.header);
}
});
上面代码是可以直接运行的。 这个例子中agent.get(url).end(callback)是一个异步api调用, 在回调函数里面定义自己的代码。 回掉函数传回来两个数据,err和res。 如果不出问题的话res对象是完整的http响应的内容,如果出错的话出错信息会保存在err对象中。
这里为了演示只是在控制台打印传回来的res的status和header (注:superagent是个很不错的http客户端,可以去github了解其提供的具体功能)。
 
顺带提一下: 异步函数不能像同步代码一样用try...catch捕获异常,所以这里有一个约定俗成,就是回掉函数一般需要有两个参数,上面superagent的例子中就是err和res,一般第一个参数是error,当异步代码发生异常的时候用这个参数返回异常详细信息, 第二个参数才是返回的有用数据,当没有异常的时候用它来返回,即superagent的例子中的res,返回的是http响应内容。
 
异步嵌套:
 
在同步代码中,如果有三个函数顺序执行,前一个的输出作为后一个的输入,只要按顺序执行三个函数即可。但在异步代码中,要做到这个就有点麻烦了。具体的原因就是因为不能像同步函数一样用return来返回值,而必须用回掉函数。所以,第二个函数要在第一个函数的回调中调用,同样第三个函数要在第二个函数的回调中调用,这就是所谓的回调嵌套。下面的例子是要先从a.txt读取其内容,然后从b.txt读取其内容,最后把两个内容合并写入ab.txt,完了后控制台打印:read and write done!
 
var fs = require('fs');

fs.readFile('./a.txt', function(err1, data1) {
fs.readFile('./b.txt', function(err2, data2) {
fs.writeFile('./ab.txt', data1 + data2, function(err) {
console.log('read and write done!');
});
});
});
 
三个异步函数嵌套看起来挺简单的。设想一下,如果有5个,10个甚至更多的异步函数要顺序执行,那要嵌套多少层呢?(大家都不喜欢身材横着长吧哈哈)说实话相当恐怖,代码会变得异常难读,难调试,难维护。这就是所谓的回调地狱或者callback hell。正是为了解决这个问题,才有了后面两节要讲的内容,用promise或generator进行异步流程管理。异步流程管理说白了就是为了解决回调地狱的问题。所以说任何事情都有两面性,异步编程有它独特的优势,却也同时遇到了同步编程根本不会有的代码组织难题。
 
思考题:
 
自己写异步函数还有一种经常遇到的情况,就是在循环中,比如for循环中不断调用异步函数asyncFn,因为每次循环调用asyncFn你都不知道它会运行到什么时候,这种情况下你的foo函数什么时候调用回掉函数来返回最终结果? 大家可以用读写文件来试验,使用fs.readFile这个函数来循环读取某个目录下面所有的文件内容,最后用fs.writeFile合并写到一个新的文件里面。答案下回分解 :)
 
function foo(arr, callback) {
for(var i=0; i<arr.length; ++i) {
asyncFn(function() {
你的代码;
})
}
}

上一篇: Javascript异步编程之一异步原理

转载请标明出处: http://www.cnblogs.com/chrischjh/p/4667713.html

Javascript异步编程之二回调函数的更多相关文章

  1. JavaScript 异步编程(二):Promise

    PromiseState Promise 有一个 [[PromiseState]] 属性,表示当前的状态,状态有 pending 和 fulfill 以及 reject. 从第一个 Promise 开 ...

  2. Javascript异步编程之一异步原理

    本系列的例子主要针对node.js环境,但浏览器端的原理应该也是类似的. 本人也是Javascript新手,把自己这段时间学习积累的要点总结下来,希望可以对同样在学习Javascript/node.j ...

  3. Javascript模块化编程(二):AMD规范

    Javascript模块化编程(二):AMD规范   作者: 阮一峰 原文地址:http://www.ruanyifeng.com/blog/2012/10/asynchronous_module_d ...

  4. JavaScript异步编程的主要解决方案—对不起,我和你不在同一个频率上

    众所周知(这也忒夸张了吧?),Javascript通过事件驱动机制,在单线程模型下,以异步的形式来实现非阻塞的IO操作.这种模式使得JavaScript在处理事务时非常高效,但这带来了很多问题,比如异 ...

  5. JavaScript异步编程原理

    众所周知,JavaScript 的执行环境是单线程的,所谓的单线程就是一次只能完成一个任务,其任务的调度方式就是排队,这就和火车站洗手间门口的等待一样,前面的那个人没有搞定,你就只能站在后面排队等着. ...

  6. [转载]Javascript异步编程的4种方法

    NodeJs的最大特性就是"异步" 目前在NodeJs里实现异步的方法中,使用“回调”是最常见的. 其实还有其他4种实现异步的方法: 在此以做记录 --- http://www.r ...

  7. Javascript异步编程的4种方法

    你可能知道,Javascript语言的执行环境是"单线程"(single thread).   所谓"单线程",就是指一次只能完成一件任务.如果有多个任务,就必 ...

  8. 转:Javascript异步编程的4种方法

    你可能知道,Javascript语言的执行环境是"单线程"(single thread). 所谓"单线程",就是指一次只能完成一件任务.如果有多个任务,就必须排 ...

  9. 深入解析Javascript异步编程

    这里深入探讨下Javascript的异步编程技术.(P.S. 本文较长,请准备好瓜子可乐 :D) 一. Javascript异步编程简介 至少在语言级别上,Javascript是单线程的,因此异步编程 ...

随机推荐

  1. myeclipise生成javadoc

    1.点击项目,右键,选择export: 点击next: 点击next:VM options中输入-encoding UTF-8 -charset UTF-8

  2. openresty + lua 1、openresty 连接 mysql,实现 crud

    最近开发一个项目,公司使用的是 openresty + lua,所以就研究了 openresty + lua.介绍的话,我就不多说了,网上太多了. 写这个博客主要是记录一下,在学习的过程中遇到的一些坑 ...

  3. [源码]一键获取windows系统登陆密码vc6版源码

    [源码]一键获取windows系统登陆密码vc6版源码支持:XP/2000/2003/WIN7/2008等 此版本编译出来的程序体积较小几十KB... 而vs版则1点几M,体积整整大了2-30倍对某些 ...

  4. rsync排除多个文件实现同步

    首先创建exclude目录放入xx.list排除文件. [root@localhost tmp]# cat /exclude/a_exclude.list a.txt lai ———————————— ...

  5. activitemq整合spring

    activitemq整合spring 一.activmq的点对点模型 pom.xml: <?xml version="1.0" encoding="UTF-8&qu ...

  6. CentOS7安装tyk(内部部署)

    CentOS7安装tyk(内部部署) 参考 官方文档 github 环境准备 #确保端口3000处于打开状态:Dashboard使用该端口来提供GUI和Developer Portal #Tyk需要P ...

  7. Lock接口

    Lock与synchronized Lock和synchronized在功能上是一样的.不过Lock提供了一些其他功能,包括定时的锁等待.可中断的锁等待.公平性,以及实现非块结构的加锁. 从性能上Lo ...

  8. (剑指Offer)面试题45:圆圈中最后剩下的数字

    题目: 0,1,...n-1这n个数字排成一个圆圈,从数字0开始每次从这个圆圈里删除第m个数字,求出这个圆圈里剩下的最后一个数字. 思路: 1.环形链表模拟圆圈 创建一个n个节点的环形链表,然后每次在 ...

  9. 逆向知识之CS辅助/外挂专题.2.实现CS1.6透视原理

    逆向知识之CS辅助/外挂专题.2.实现CS1.6透视原理 一丶透视简介 我们涉及到FPS游戏.免不了说透视.自瞄什么的. 在CS1.6中. 有OpenGl.也有D3D. 透视的方法很多. gl透视(也 ...

  10. Ubuntu中root的默认密码

    原文地址:http://www.cnblogs.com/xuyingying/archive/2008/10/16/1312584.html 安装完Ubuntu后忽然意识到没有设 置root密码,不知 ...