Node.js 异步编程的直接体现就是回调。
异步编程依托于回调来实现,但不能说使用了回调后程序就异步化了。
回调函数在完成任务后就会被调用,Node 使用了大量的回调函数,Node 所有 API 都支持回调函数。
例如,我们可以一边读取文件,一边执行其他命令,在文件读取完成后,我们将文件内容作为回调函数的参数返回。这样在执行代码时就没有阻塞或等待文件 I/O 操作。这就大大提高了 Node.js 的性能,可以处理大量的并发请求。
回调函数一般作为函数的最后一个参数出现
function foo1(name, age, callback) { }
function foo2(value, callback1, callback2) { }
来源:菜鸟教程
-
基于node.js的web应用实践回调函数(异步回调)
很多时候web应用为了简化应用的复杂性会采取分层设计常见的有mvc模型,如果是基于前后端分离的node.js应用。这里抛开v(view)视图层不说,来说说node.js回调函数在m(model)模型层和c(Controller)控制器层的应用。
1.1.控制器层:(是应用程序中处理用户交互的部分。
通常控制器负责从视图读取数据,控制用户输入,并向模型发送数据。来源:百度百科)在我实际实践过程中控制器也是调度层根据用户输入调度到相应的模型层去处理,模型层处理完毕后需要通知控制器层处理结果方便控制器层进行下一步的处理调度(通过回调函数通知控制器层)。1.2.模型层:(是应用程序中用于处理应用程序数据逻辑的部分。通常模型对象负责在数据库中存取数据。来源:百度百科)在node.jsweb应用系统中实际上并不是每一个模型处理完的结果都要通过回调函数通知控制器层,因为这可能是最后一步的业务处理结果,处理完毕后直接发送到客户端了,这里就不需要用到回调函数。(在我的例子里模型层叫服务层)
1.3.代码example:
1.3.1.路由配置
const ExampleControllers = require('../app/controllers/Admin/Example');
router.get('/example/test_callback',ExampleControllers.test_callback););
1.3.2.控制器层代码
const ExampleService = require("../../service/Admin/Example");
const Common = require("../../util/Common");
const Base = require("../../service/Admin/Base"));
/**
* 测试callback(业务控制器调度层)
* @param req
* @param res
* @param next
* @param callback_result 回调结果
*/
exports.test_callback = function(req , res , next , callback_result={}){
if(JSON.stringify(callback_result)=='{}'){
ExampleService.business_logic(req , res , next,exports.test_callback);
}else {
//业务返回结果
console.log("业务返回结果");
console.log(callback_result);
console.log("结束时间:"+Common.nowtime());
}
};
1.3.3.模型层代码(服务层)
const Common = require("../../util/Common");
var fs = require("fs");
var iconv = require('iconv-lite');
var deasync = require('deasync');
/**
* 业务逻辑处理business_logic(业务实际服务层)
* @param req
* @param res
* @param next
* @param callback 业务逻辑处理回调函数(非阻塞)
*/
exports.business_logic = function(req , res , next , callback){
var result={};//返回业务处理汇总结果
var count=1;//业务处理计数器
console.log("开始时间:"+Common.nowtime());
//下面5个业务并发执行并不需要等待谁执行完(每次执行返回的顺序结果都是不同的)
fs.readFile('input1.txt', function (err, data) {
if (err) return console.error(err);
var buf = new Buffer(data,'binary');
var str = iconv.decode(buf,'GBK');
if(count>=5){
result['rs1']=str;
callback(req , res , next,result)
}else{
count++;
result['rs1']=str;
}
});
fs.readFile('input2.txt', function (err, data) {
if (err) return console.error(err);
var buf = new Buffer(data,'binary');
var str = iconv.decode(buf,'GBK');
if(count>=5){
result['rs2']=str;
callback(req , res , next,result)
}else{
count++;
result['rs2']=str;
}
});
fs.readFile('input3.txt', function (err, data) {
if (err) return console.error(err);
var buf = new Buffer(data,'binary');
var str = iconv.decode(buf,'GBK');
if(count>=5){
result['rs3']=str;
callback(req , res , next,result)
}else{
count++;
result['rs3']=str;
}
});
fs.readFile('input4.txt', function (err, data) {
if (err) return console.error(err);
var buf = new Buffer(data,'binary');
var str = iconv.decode(buf,'GBK');
if(count>=5){
result['rs4']=str;
callback(req , res , next,result)
}else{
count++;
result['rs4']=str;
}
});
fs.readFile('input5.txt', function (err, data) {
if (err) return console.error(err);
var buf = new Buffer(data,'binary');
var str = iconv.decode(buf,'GBK');
if(count>=5){
result['r5']=str;
callback(req , res , next,result)
}else{
count++;
result['r5']=str;
}
});
};
1.3.4.运行两次代码运行结果
(异步非阻塞返回业务数据)
开始时间:1565588788
业务返回结果
{ rs2: '当月支出:5000',
rs1: '银行余额:10000',
rs4: '总转账:150000',
rs5: '开户日期:2013/5/23',
rs3: '当月收入6000' }
结束时间:1565588788
GET /admin/example/test_callback - - ms - -
开始时间:1565588791
业务返回结果
{ rs1: '银行余额:10000',
rs2: '当月支出:5000',
rs3: '当月收入6000',
rs4: '总转账:150000',
rs5: '开户日期:2013/5/23' }
结束时间:1565588791
GET /admin/example/test_callback - - ms - -
1.3.5.代码运行释义:
用户通过访问url:
http://127.0.0.1/admin/example/test_callback1发起一个请求首先请求到达控制器层test_callback函数,控制器层test_callback函数最后一个参数默认为(callback_result={})一个空对象,当空对象转为字符串时刚好等于字符串"{}",所逻辑判断结果就是去调用服务层函数business_logic。
你可以看到business_logic函数最后一个参数为exports.test_callback相当于控制器方法test_callback把自身作为回调函数传入business_logic,business_logic在通过这个回调函数告诉exports.test_callback业务处理结果方便控制器层后续调度。(相当于你叫别人介绍一个妹子,你把自己联系方式告诉别人,别人有妹子介绍给你了再通过你留的联系方式通知你,在代码里node.js里呢就叫回调函数)
在看看business_logic代码执行5个并发处理业务在汇总(异步执行),再通过回调函数返回业务处理结果。你可以看到1.3.4里两次返回的数据顺序是不一样的,因为代码是异步执行的,所以每次运行时input1到input5都有可能是最后一步触发异步回调的函数。因为服务层是异步运行再通知控制器层,so这就是我理解的异步回调。
2.基于node.js的web应用实践回调函数(同步回调)
2.1代码example:
2.1.1.路由配置
const ExampleControllers = require('../app/controllers/Admin/Example');
//从实际应用角度去理解Node.js回调函数
router.get('/example/test_callback1',ExampleControllers.test_callback1);
2.1.2.控制器层代码
const ExampleService = require("../../service/Admin/Example");
const Common = require("../../util/Common");
const Base = require("../../service/Admin/Base");
/**
* 测试callback(业务控制器调度层)
* @param req
* @param res
* @param next
* @param callback_result 回调结果
*/
exports.test_callback1 = function(req , res , next , callback_result={}){
if(JSON.stringify(callback_result)=='{}'){
ExampleService.business_logic1(req , res , next,exports.test_callback1);
}else {
//业务返回结果
console.log("业务返回结果1");
console.log(callback_result);
console.log("结束时间1:"+Common.nowtime());
}
};
2.1.3.模型层代码(服务层)
const Common = require("../../util/Common");
var fs = require("fs");
var iconv = require('iconv-lite');
var deasync = require('deasync');
/**
* 业务逻辑处理business_logic(业务实际服务层)
* @param req
* @param res
* @param next
* @param callback 业务逻辑处理回调函数(阻塞)
*/
exports.business_logic1 = function(req , res , next , callback){
var result={};//返回业务处理汇总结果
console.log("开始时间1:"+Common.nowtime());
//下面5个业务按顺序执行(当每个业务处理的前提依赖前面的结果就必须让代码按顺序执行)
fs.readFile('input1.txt', function (err, data) {
if (err) return console.error(err);
var buf = new Buffer(data,'binary');
var str = iconv.decode(buf,'GBK');
result['rs1']=str;
fs.readFile('input2.txt', function (err, data) {
if (err) return console.error(err);
var buf = new Buffer(data,'binary');
var str = iconv.decode(buf,'GBK');
result['rs2']=str;
fs.readFile('input3.txt', function (err, data) {
if (err) return console.error(err);
var buf = new Buffer(data,'binary');
var str = iconv.decode(buf,'GBK');
result['rs3']=str;
fs.readFile('input4.txt', function (err, data) {
if (err) return console.error(err);
var buf = new Buffer(data,'binary');
var str = iconv.decode(buf,'GBK');
result['rs4']=str;
fs.readFile('input5.txt', function (err, data) {
if (err) return console.error(err);
var buf = new Buffer(data,'binary');
var str = iconv.decode(buf,'GBK');
result['r5']=str;
callback(req , res , next,result)
});
});
});
});
});
};
2.1.4.运行两次代码运行结果
(同步阻塞返回业务数据)
开始时间1:1565591038
业务返回结果1
{ rs1: '银行余额:10000',
rs2: '当月支出:5000',
rs3: '当月收入6000',
rs4: '总转账:150000',
rs5: '开户日期:2013/5/23' }
结束时间1:1565591038
GET /admin/example/test_callback1 - - ms - -
开始时间1:1565591041
业务返回结果1
{ rs1: '银行余额:10000',
rs2: '当月支出:5000',
rs3: '当月收入6000',
rs4: '总转账:150000',
rs5: '开户日期:2013/5/23' }
结束时间1:1565591041
GET /admin/example/test_callback1 - - ms - -
2.1.5.运行两次代码运行结果:
控制器层代码略去不讲,可以看到服务层business_logic1函数读取input1到input5是按顺序执行的,如果在某个业务场景每一个查询或者每一个业务处理都依赖于上一个查询或者处理结果这时就不能让这一区间的代码执行异步操作了,最后在代码例子里永远都是执行到input5才通过回调函数通知控制器层处理结果,由于服务层业务处理是同步执行的(顺序执行),so这就是我理解的同步回调