浅谈Node异步编程的机制


Posted in Javascript onOctober 18, 2017

本文介绍了Node异步编程,分享给大家,具体如下:

目前的异步编程主要解决方案有:

  • 事件发布/订阅模式
  • Promise/Deferred模式
  • 流程控制库

事件发布/订阅模式

Node自身提供了events模块,可以轻松实现事件的发布/订阅

//订阅
emmiter.on("event1",function(message){
  console.log(message);
})
//发布
emmiter.emit("event1","I am mesaage!");

侦听器可以很灵活地添加和删除,使得事件和具体处理逻辑之间可以很轻松的关联和解耦

事件发布/订阅模式常常用来解耦业务逻辑,事件发布者无需关注订阅的侦听器如何实现业务逻辑,甚至不用关注有多少个侦听器存在,数据通过消息的方式可以很灵活的进行传递。

下面的HTTP就是典型的应用场景

var req = http.request(options,function(res){
  res.on('data',function(chunk){
    console.log('Body:'+ chunk);
  })
  res.on('end',function(){
    //TODO
  })
})

如果一个事件添加了超过10个侦听器,将会得到一条警告,可以通过调用emmite.setMaxListeners(0)将这个限制去掉

继承events模块

var events = require('events');
function Stream(){
  events.EventEmiiter.call(this);
}
util.inherits(Stream,events.EventEmitter);

利用事件队列解决雪崩问题

所谓雪崩问题,就是在高访问量,大并发量的情况下缓存失效的情况,此时大量的请求同时融入数据库中,数据库无法同时承受如此大的查询请求,进而往前影响到网站整体的响应速度

解决方案:

var proxy = new events.EventEmitter();
var status = "ready"; 
var seletc = function(callback){
  proxy.once("selected",callback);//为每次请求订阅这个查询时间,推入事件回调函数队列
  if(status === 'ready'){ 
    status = 'pending';//设置状态为进行中以防止引起多次查询操作
    db.select("SQL",function(results){
      proxy.emit("selected",results); //查询操作完成后发布时间
      status = 'ready';//重新定义为已准备状态
    })
  }
}

多异步之间的协作方案

以上情况事件与侦听器的关系都是一对多的,但在异步编程中,也会出现事件与侦听器多对一的情况。

这里以渲染页面所需要的模板读取、数据读取和本地化资源读取为例简要介绍一下

var count = 0 ;
var results = {};
var done = function(key,value){
  result[key] = value;
  count++;
  if(count === 3){
    render(results);
  }
}
fs.readFile(template_path,"utf8",function(err,template){
  done('template',template)
})
db.query(sql,function(err,data){
  done('data',data);
})
l10n.get(function(err,resources){
  done('resources',resources)
})

偏函数方案

var after = function(times,callback){
  var count = 0, result = {};
  return function(key,value){
    results[key] = value;
    count++;
    if(count === times){
      callback(results);
    }
  }
}
var done = after(times,render);
var emitter = new events.Emitter();
emitter.on('done',done);  //一个侦听器
emitter.on('done',other);  //如果业务增长,可以完成多对多的方案

fs.readFile(template_path,"utf8",function(err,template){
  emitter.emit('done','template',template);
})
db.query(sql,function(err,data){
  emitter.emit('done','data',data);
})
l10n.get(function(err,resources){
  emitter.emit('done','resources',resources)
})

引入EventProxy模块方案

var proxy = new EventProxy();
proxy.all('template','data','resources',function(template,data,resources){
  //TODO
})
fs.readFile(template_path,'utf8',function(err,template){
  proxy.emit('template',template);
})
db.query(sql,function(err,data){
  proxy.emit('data',data);
})
l10n.get(function(err,resources){
  proxy.emit('resources',resources);
})

Promise/Deferred模式

以上使用事件的方式时,执行流程都需要被预先设定,这是发布/订阅模式的运行机制所决定的。

$.get('/api',{
  success:onSuccess,
  err:onError,
  complete:onComplete
})
//需要严谨设置目标

那么是否有一种先执行异步调用,延迟传递处理的方式的?接下来要说的就是针对这种情况的方式:Promise/Deferred模式

Promise/A

Promise/A提议对单个异步操作做出了这样的抽象定义:

  • Promise操作只会处在三种状态的一种:未完成态,完成态和失败态。
  • Promise的状态只会出现从未完成态向完成态或失败态转化,不能逆反,完成态和失败态不能相互转化
  • Promise的状态一旦转化,就不能被更改。

一个Promise对象只要具备then()即可

  • 接受完成态、错误态的回调方法
  • 可选地支持progress事件回调作为第三个方法
  • then()方法只接受function对象,其余对象将被忽略
  • then()方法继续返回Promise对象,以实现链式调用

通过Node的events模块来模拟一个Promise的实现

var Promise = function(){
  EventEmitter.call(this)
}
util.inherits(Promise,EventEmitter);

Promise.prototype.then = function(fulfilledHandler,errHandler,progeressHandler){
  if(typeof fulfilledHandler === 'function'){
    this.once('success',fulfilledHandler); //实现监听对应事件
  }
  if(typeof errorHandler === 'function'){
    this.once('error',errorHandler)
  }
  if(typeof progressHandler === 'function'){
    this.on('progress',progressHandler);
  }
  return this;
}

以上通过then()将回调函数存放起来,接下来就是等待success、error、progress事件被触发,实现这个功能的对象称为Deferred对象,即延迟对象。

var Deferred = function(){
  this.state = 'unfulfilled';
  this.promise = new Promise();
}
Deferred.prototype.resolve = function(obj){ //当异步完成后可将resolve作为回调函数,触发相关事件
  this.state = 'fulfilled';
  this.promise.emit('success',obj);
}
Deferred.prototype.reject = function(err){
  this.state = 'failed';
  this.promise.emit('error',err);
}
Deferred.prototype.progress = function(data){
  this.promise.emit('progress',data)
}

因此,可以对一个典型的响应对象进行封装

res.setEncoding('utf8');
res.on('data',function(chunk){
  console.log("Body:" + chunk);
})
res.on('end',function(){
  //done
})
res.on('error',function(err){
  //error
}

转换成

res.then(function(){
  //done
},function(err){
  //error
},function(chunk){
  console.log('Body:' + chunk);
})

要完成上面的转换,首先需要对res对象进行封装,对data,end,error等事件进行promisify

var promisify = function(res){
  var deferred = new Deferred(); //创建一个延迟对象来在res的异步完成回调中发布相关事件
  var result = ''; //用来在progress中持续接收数据
  res.on('data',function(chunk){ //res的异步操作,回调中发布事件
    result += chunk;
    deferred.progress(chunk);
  })
  res.on('end',function(){    
    deferred.resolve(result);
  })
  res.on('error',function(err){
    deferred.reject(err);
  });
  return deferred.promise   //返回deferred.promise,让外界不能改变deferred的状态,只能让promise的then()方法去接收外界来侦听相关事件。
}

promisify(res).then(function(){
  //done
},function(err){
  //error
},function(chunk){
  console.log('Body:' + chunk);
})

以上,它将业务中不可变的部分封装在了Deferred中,将可变的部分交给了Promise

Promise中的多异步协作

Deferred.prototype.all = function(promises){
  var count = promises.length; //记录传进的promise的个数
  var that = this; //保存调用all的对象
  var results = [];//存放所有promise完成的结果
  promises.forEach(function(promise,i){//对promises逐个进行调用
    promise.then(function(data){//每个promise成功之后,存放结果到result中,count--,直到所有promise被处理完了,才出发deferred的resolve方法,发布事件,传递结果出去
      count--;
      result[i] = data;
      if(count === 0){
        that.resolve(results);
      }
    },function(err){
      that.reject(err);
    });
  });
  return this.promise; //返回promise来让外界侦听这个deferred发布的事件。
}

var promise1 = readFile('foo.txt','utf-8');//这里的文件读取已经经过promise化
var promise2 = readFile('bar.txt','utf-8');
var deferred = new Deferred();
deferred.all([promise1,promise2]).thne(function(results){//promise1和promise2的then方法在deferred内部的all方法所调用,用于同步所有的promise
  //TODO
},function(err){
  //TODO
})

支持序列执行的Promise

尝试改造一下代码以实现链式调用

var Deferred = function(){
  this.promise = new Promise()
}

//完成态
Deferred.prototype.resolve = function(obj){
  var promise = this.promise;
  var handler;
  while((handler = promise.queue.shift())){
    if(handler && handler.fulfilled){
      var ret = handler.fulfilled(obj);
      if(ret && ret.isPromise){
        ret.queue = promise.queue;
        this.promise = ret;
        return;
      }
    }
  }
}

//失败态
Deferred.prototype.reject = function(err){
  var promise = this.promise;
  var handler;
  while((handler = promise.queue.shift())){
    if(handler && handler.error){
      var ret = handler.error(err);
      if(ret && ret.isPromise){
        ret.queue = promise.queue;
        this.promise = ret;
        return
      }
    }
  }
}

//生成回调函数
Deferred.prototype.callback = function(){
  var that = this;
  return function(err,file){
    if(err){
      return that.reject(err);
    }
    that.resolve(file)
  }
}

var Promise = function(){
  this.queue = []; //队列用于存储待执行的回到函数
  this.isPromise = true;
};
Promise.prototype.then = function(fulfilledHandler,errorHandler,progressHandler){
  var handler = {};
  if(typeof fulfilledHandler === 'function'){
    handler.fulfilled = fulfilledHandler;
  }
  if(typeof errorHandler === 'function'){
    handler.error = errorHandler;
  }
  this.queue.push(handler);
  return this;
}

var readFile1 = function(file,encoding){
  var deferred = new Deferred();
  fs.readFile(file,encoding,deferred.callback());
  return deferred.promise;
}
var readFile2 = function(file,encoding){
  var deferred = new Deferred();
  fs.readFile(file,encoding,deferred.callback());
  return deferred.promise;
}

readFile1('file1.txt','utf8').then(function(file1){
  return readFile2(file1.trim(),'utf8')
}).then(function(file2){
  console.log(file2)
})

流程控制库另外进行总结

参考《深入浅出node.js》一书,想学学习可以下载电子书,下载地址:https://3water.com/books/481114.html

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
jQuery focus和blur事件的应用详解
Jan 26 Javascript
JQuery.get提交页面不跳转的解决方法
Jan 13 Javascript
JavaScript 动态加载脚本和样式的方法
Apr 13 Javascript
this,this,再次讨论javascript中的this,超全面(经典)
Jan 05 Javascript
JavaScript学习笔记之数组求和方法
Mar 23 Javascript
简单掌握JavaScript中const声明常量与变量的用法
May 21 Javascript
jQuery模拟窗口抖动效果
Mar 15 Javascript
谈谈React中的Render Props模式
Dec 06 Javascript
js回调函数仿360开机
Dec 26 Javascript
在Vue中实现随hash改变响应菜单高亮
Mar 09 Javascript
JavaScript正则表达式验证登录实例
Mar 18 Javascript
详解React 元素渲染
Jul 07 Javascript
js实现随机点名系统(实例讲解)
Oct 18 #Javascript
原生JS获取元素的位置与尺寸实现方法
Oct 18 #Javascript
详谈commonjs模块与es6模块的区别
Oct 18 #Javascript
从源码看angular/material2 中 dialog模块的实现方法
Oct 18 #Javascript
详解http访问解析流程原理
Oct 18 #Javascript
js实现会跳动的日历效果(完整实例)
Oct 18 #Javascript
打字效果动画的4种实现方法(超简单)
Oct 18 #Javascript
You might like
Windows下IIS6/Apache2.2.4+MySQL5.2+PHP5.2.1安装配置方法
2007/05/03 PHP
PHP可变函数的使用详解
2013/06/14 PHP
Yii数据读取与跳转参数传递用法实例分析
2016/07/12 PHP
OAuth认证协议中的HMACSHA1加密算法(实例)
2017/10/25 PHP
phpMyAdmin通过密码漏洞留后门文件
2018/11/20 PHP
基于jQuery的固定表格头部的代码(IE6,7,8测试通过)
2010/05/18 Javascript
Javascript的各种节点操作实例演示代码
2012/06/27 Javascript
变量声明时命名与变量作为对象属性时命名的区别解析
2013/12/06 Javascript
JQuery中属性过滤选择器用法实例分析
2015/05/18 Javascript
浅谈javascript原型链与继承
2015/07/13 Javascript
原生js与jQuery实现简单的tab切换特效对比
2015/07/30 Javascript
Bootstrap源码解读排版(1)
2016/12/23 Javascript
JS实现密码框的显示密码和隐藏密码功能示例
2016/12/26 Javascript
vue2.0构建单页应用最佳实战
2017/04/01 Javascript
原生js FileReader对象实现图片上传本地预览效果
2020/03/27 Javascript
React Native react-navigation 导航使用详解
2017/12/01 Javascript
javascript少儿编程关于返回值的函数内容
2018/05/27 Javascript
layui 对table中的数据进行转义的实例
2019/09/12 Javascript
解决LayUI数据表格复选框不居中显示的问题
2019/09/25 Javascript
使用vue-cli3+typescript的项目模板创建工程的教程
2020/02/28 Javascript
用python读写excel的方法
2014/11/18 Python
Python3里的super()和__class__使用介绍
2015/04/23 Python
python3基于OpenCV实现证件照背景替换
2018/07/18 Python
Python对象转换为json的方法步骤
2019/04/25 Python
Python统计一个字符串中每个字符出现了多少次的方法【字符串转换为列表再统计】
2019/05/05 Python
Python可迭代对象操作示例
2019/05/07 Python
Python Pandas数据结构简单介绍
2019/07/03 Python
多版本python的pip 升级后, pip2 pip3 与python版本失配解决方法
2019/09/11 Python
会计学专业求职信
2014/07/17 职场文书
纪检干部对照检查材料
2014/08/22 职场文书
弄虚作假心得体会
2014/09/10 职场文书
读后感作文评语
2014/12/25 职场文书
三年级学生期末评语
2014/12/26 职场文书
Django实现WebSocket在线聊天室功能(channels库)
2021/09/25 Python
SQL 聚合、分组和排序
2021/11/11 MySQL
Python Matplotlib绘制两个Y轴图像
2022/04/13 Python