萌新脱坑指南 - JavaScript回调函数&Node.js异步回调

这两天要给任狗解释什么是回调函数,想了想应该整理成文章,方便看,也当理清自己的思路了。

博客主题偷懒到现在没写完,代码看着可能有点不舒服,还请将就着看。

什么是回调函数

顾名思义,回调,回头再调用它(什么鬼 。
JS中的回调函数,作为萌新,我们会在这样的地方看见它:

app.get('/',function(req,res){
    res.send("hello world");
})

在这里面,作为 app.get方法(此处感谢老姐夫@faceair纠正)的第二个参数的,是一个函数,而这个,我们可以叫它回调函数。

回调函数是咋用的

我们来假设一个应用场景。

  1. 我们有两个初始参数
  2. 我们为二者求和
  3. 求和结果翻倍
  4. 调教
// 普通柴犬会先写个函数,返回其求和
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);

按照正常的执行顺序,应该是这样运行的:

  1. 定义doge为0;
  2. 进行读文件操作,读取文件内容,读取成功后把文件内容的长度赋值给doge;
  3. 在控制台输出hello,doge : 30
  4. 在浏览器的页面里输出hello,doge : 30

抱着这样的信心,测试了一下,会被光速打脸。
真正的输出结果是:

  1. 在控制台输出hello,doge : 30
  2. 在浏览器的页面里输出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机制及原理理解初步(具体为什么会有点菜特性可以参考这篇文章)

标签: none

已有 3 条评论

  1. 狗哥英明,我已拜读(ಥ_ಥ)

    1. 首先不是你师傅,其次你文章里的代码缩进赶紧重写。。。

  2. async, await, promise 更好玩(

添加新评论