萌新脱坑指南 - JavaScript回调函数&Node.js异步回调
序
这两天要给任狗解释什么是回调函数,想了想应该整理成文章,方便看,也当理清自己的思路了。
博客主题偷懒到现在没写完,代码看着可能有点不舒服,还请将就着看。
什么是回调函数
顾名思义,回调,回头再调用它(什么鬼 。
JS中的回调函数,作为萌新,我们会在这样的地方看见它:
app.get('/',function(req,res){
res.send("hello world");
})
在这里面,作为 app.get
方法(此处感谢老姐夫@faceair纠正)的第二个参数的,是一个函数,而这个,我们可以叫它回调函数。
回调函数是咋用的
我们来假设一个应用场景。
- 我们有两个初始参数
- 我们为二者求和
- 求和结果翻倍
- 调教
// 普通柴犬会先写个函数,返回其求和
function sum(a,b){
var sum = a+b;
return sum;
console.log("sum函数结束");
}
// 然后用一个变量获取函数的返回值
var doge = sum(116,117);
// 数值翻倍
doge *= 2;
// 然后再随意地调教调教它
console.log(doge);
以上的代码,根据执行顺序,会在var doge = sum(116,117)
的时候输出sum函数结束
,最后才输出doge的最终值
这是一种普通的处理方式,顺序执行。然后我们用回调的方法嵌套执行:
// 普通柴犬会先写个函数,其中callback就是你需要写进参数的回调函数
function sum(a,b,callback){
var sum = a+b;
// 进行处理之后,调用回调函数,并把求得的和作为参数传进了它
callback(sum);
console.log("sum函数结束");
}
// 然后搞搞搞,前两个是参数,后面的就是回调函数
sum(233,233,function(data){
// 然后用一个变量获取函数的返回值
var doge = data;
// 数值翻倍
doge *= 2;
// 然后再随意地调教调教它
console.log(doge);
});
这里会先输出doge的值,然后才输出“sum函数结束”。
这就是回调函数最基本的形态和原理。
node.js的异步回调
我们使用node.js的时候,经常会用到回调函数,它在形态上和我们刚刚写的类似,但是它有着异步的特性。
这个特性,我们可以理解为“点菜”特性。
我们先扔出一个express中很常见的例子。
var express = require("express");
var fs = require("fs");// fs是node的文件系统模块
var app = express();
app.get('/',function(req,res){
var doge = 0;
// fs.readFile方法是一种读取文件的方法,未出错会把文件的内容赋值到data这个位置
fs.readFile("./test.md",function(err,data){
if(err){
console.log("error : "+err)
}else{
doge = data.length;
console.log("hello,doge : "+doge);
}
});
// res.send的内容会输出到页面中
res.send("hello,doge : "+doge);
});
app.listen(23333);
这里我们使用express进行了一个简单的服务构建,当启动的时候,访问localhost:23333/
,我们会得到res.send
的参数。下面我们忽略杂项只看正片内容:
var doge = 0;
fs.readFile("./test.md",function(err,data){
if(err){
console.log("error : "+err)
}else{
doge = data.length;//根据我的测试文件,这里假设文件内容的长度是30
console.log("hello,doge : "+doge);
}
});
res.send("hello,doge : "+doge);
按照正常的执行顺序,应该是这样运行的:
- 定义doge为0;
- 进行读文件操作,读取文件内容,读取成功后把文件内容的长度赋值给doge;
- 在控制台输出
hello,doge : 30
; - 在浏览器的页面里输出
hello,doge : 30
。
抱着这样的信心,测试了一下,会被光速打脸。
真正的输出结果是:
- 在控制台输出
hello,doge : 30
; - 在浏览器的页面里输出
hello,doge : 0
。
这是为什么呢?这正是刚刚我们提到的“node.js的'点菜'特性”。
在node.js中,我们能见到的很多模块的、需要写回调函数的方法,都具有这个点菜特性,比如刚刚的fs.readFile()
。
我们在餐馆中,我们进行了一次点菜,但是这不影响我们继续点其他的菜,或者结束点菜开始聊天、等到菜上来再开吃,对吧?
所以,我们的餐馆行为,是这样的:
进入餐馆 -> 点菜 -> 谈笑风生 ->(菜做好之后)吃菜
刚刚的行为,影射到我们的node代码中,其实是这样的:
//进入餐馆;
点菜("冷吃兔",function(菜){
吃(菜);
})
谈笑风生();
也就是说,我们提前定义了一个回调函数,菜点好了,在做菜完成之后才去执行function(菜){吃(菜);}
,而我们会开始谈笑风生。
重新审视我们刚刚的代码:
var doge = 0;
// 我们现在点了个菜
fs.readFile("./test.md",function(err,data){
// 这里面,是菜做好了,产生了err和data值之后执行的内容
if(err){
console.log("error : "+err)
}else{
doge = data.length;
console.log("hello,doge : "+doge);
}
});
// 这是点菜之后 执行的内容
res.send("hello,doge : "+doge);
这就是node的“点菜”特性,我们通常匪夷所思地叫它“异步回调”,其实理解为点菜,会更简单一些。正是因为有这样的特性,所以在基础的node写法中,我们总是会嵌套起很多层的回调函数,用以防止逻辑顺序不能正确执行。
在前人的不懈努力后,我们已经有了很多的对于异步回调嵌套问题的解决方案,比如co,Promise,yield,async等等等等,具体内容还请异步baidu/google。
总结
以上是关于JS的回调函数和Node.js的异步回调的一些脱坑帮助,一点拙见,如果哪里出现错误请邮箱或者评论告知。
感谢在创作过程中提供灵魂帮助的@名侦探羊神,事实上“点菜”这个生动的比喻正是羊神提出的。
同样感谢帮我纠错的老姐夫@faceair,上文中数处错误已在老姐夫指导下进行了修改。
参考:
· 文件系统 (fs)-Node.js中文网
· Node.js机制及原理理解初步(具体为什么会有点菜特性可以参考这篇文章)