浅谈Express异步进化史


Posted in Javascript onSeptember 09, 2017

1、导言

在 Javascript 的世界里,异步(由于JavaScript的单线程运行,所以JavaScript中的异步是可以阻塞的)无处不在。

Express 是 node 环境中非常流行的Web服务端框架,有很大比例的 Node Web应用 采用了 Express。

当使用 JavaScript 编写服务端代码时,我们无可避免的会大量使用到异步。随着 JavaScript、Node 的进化,我们的异步处理方式,也就随之进化。

接下来,我们就来看看 Express 中异步处理的进化过程。

2、JavaScript的异步处理

在异步的世界里,我们需要想办法获取的异步方法完毕的通知,那在 JavaScript 中,会有哪些方式呢?

2.1、回调

回调是 JS 中最原始,也是最古老的异步通知机制。

function asyncFn(callback) {
 // 利用setTimeout模拟异步
 setTimeout(function () {
  console.log('执行完毕');
  callback(); // 发通知
 }, 2000);
}

asyncFn(function () {
 console.log('我会在2s后输出');
});

2.2、事件监听

要获取结果的函数,监听某个时间。在异步方法完成后,触发该事件,达到通知的效果。

2.3、发布/订阅

通过观察者模式,在异步完成时,修改发布者。这个时候,发布者会把变更通知到订阅者。

2.4、Promise

Promise 是回调函数的改进。使用它, 我们可以将异步平行化,避免回调地狱。

function asyncFn() {
 return new Promise((resolve, reject) => {
  // 利用setTimeout模拟异步
  setTimeout(function () {
   console.log('执行完毕');
   resolve(); // 发通知(是否有感觉到回调的影子?)
  }, 2000);
 });
}

asyncFn()
 .then(function () {
  console.log('我会在2s后输出');
 });

2.5、生成器(Generator)

Generator 函数是 ES6 提供的一种异步编程解决方案。

以下代码只是简单演示,实际上 Generator 的使用过程,相对是比较复杂的,这是另外一个话题,本文暂且不表。

function asyncFn() {
 return new Promise((resolve, reject) => {
  // 利用setTimeout模拟异步
  setTimeout(function () {
   console.log('执行完毕');
   resolve(); // 发通知(是否有感觉到回调的影子?)
  }, 2000);
 });
}

function* generatorSync() {
 var result = yield asyncFn();
}

var g = generatorSync();
g.next().value.then(()=>{
 console.log('我会在2s后输出');
});

2.6、async...await

可以说是当前 JavaScript 中,处理异步的最佳方案。

function asyncFn() {
 return new Promise((resolve, reject) => {
  // 利用setTimeout模拟异步
  setTimeout(function () {
   console.log('执行完毕');
   resolve(); // 发通知(是否有感觉到回调的影子?)
  }, 2000);
 });
}

async function run(){
 await asyncFn();
 console.log('我会在2s后输出');
}

run();

3、Express中的异步处理

在Express中,我们一般常用的是方案是:回调函数、Promise、以及async...await。

为了搭建演示环境,通过 express-generator 初始化一个express项目。一般的服务端项目,都是路由调用业务逻辑。所以,我们也遵循这个原则:

打开 routs/index.js,我们会看到如下内容,以下Demo就以此文件来做演示。

var express = require('express');
var router = express.Router();

/* GET home page. */
router.get('/', function(req, res, next) {
 res.render('index', { title: 'Express' });
});

module.exports = router;

3.1、回调函数处理Express异步逻辑

在 Express 中,路由可以加载多个中间件,所以我们可以把业务逻辑按照中间件的写法进行编写。这样通过一层层的next,就能非常方便的拆分异步逻辑。

var express = require('express');
var router = express.Router();

function asyncFn(req, res, next) {
 setTimeout(() => {
  req.user = {}; // 设置当前请求的用户
  next();
 }, 2000);
}

function asyncFn2(req, res, next) {
 setTimeout(() => {
  req.auth = {}; // 设置用户权限
  next();
 }, 2000);
}

function asyncFn3(req, res, next) {
 setTimeout(() => {
  res.locals = { title: 'Express Async Test' }; // 设置数据
  res.render('index'); // 响应
 }, 2000);
}

/* GET home page. */
router.get('/', asyncFn, asyncFn2, asyncFn3); // 一步步执行中间件

module.exports = router;

3.2、Promise 处理Express异步逻辑

该方案中,将多个业务逻辑,包装为返回 Promise 的函数。通过业务方法进行组合调用,以达到一进一出的效果。

var express = require('express');
var router = express.Router();

function asyncFn(req, res) {
 return new Promise((resolve, reject) => {
  setTimeout(() => {
   req.user = {}; // 设置当前请求的用户
   resolve(req);
  }, 2000);
 });
}

function asyncFn2(req) {
 return new Promise((resolve, reject) => {
  setTimeout(() => {
   req.auth = {}; // 设置用户权限
   resolve();
  }, 2000);
 });
}

function asyncFn3(res) {
 return new Promise((resolve, reject) => {
  setTimeout(() => {
   res.locals = { title: 'Express Async Test' }; // 设置数据
   res.render('index'); // 响应
  }, 2000);
 });
}

function doBizAsync(req, res, next) {
 asyncFn(req)
  .then(() => asyncFn2(req))
  .then(() => asyncFn3(res))
  .catch(next); // 统一异常处理
};

/* GET home page. */
router.get('/', doBizAsync);

module.exports = router;

3.3、async...await 处理Express异步逻辑

实际上,该方案也是需要 Promise 的支持,只是写法上,更直观,错误处理也更直接。

需要注意的是,Express是早期的方案,没有对async...await进行全局错误处理,所以可以采用包装方式,进行处理。

var express = require('express');
var router = express.Router();

function asyncFn(req) {
 return new Promise((resolve, reject) => {
  setTimeout(() => {
   req.user = {}; // 设置当前请求的用户
   resolve(req);
  }, 2000);
 });
}

function asyncFn2(req) {
 return new Promise((resolve, reject) => {
  setTimeout(() => {
   req.auth = {}; // 设置用户权限
   resolve();
  }, 2000);
 });
}

function asyncFn3(res) {
 return new Promise((resolve, reject) => {
  setTimeout(() => {

  }, 2000);
 });
}

async function doBizAsync(req, res, next) {
 var result = await asyncFn(req);
 var result2 = await asyncFn2(req);
 res.locals = { title: 'Express Async Test' }; // 设置数据
 res.render('index'); // 响应
};

const tools = {
 asyncWrap(fn) {
  return (req, res, next) => {
   fn(req, res, next).catch(next); // async...await在Express中的错误处理
  }
 }
};

/* GET home page. */
router.get('/', tools.asyncWrap(doBizAsync)); // 需要用工具方法包裹一下

module.exports = router;

4、总结

虽然 koa 对更新、更好的用法(koa是generator,koa2原生async)支持的更好。但作为从 node 0.x 开始跟的我,对 Express 还是有特殊的好感。

以上的一些方案,已经与 koa 中使用无异,配合 Express 庞大的生态圈,无异于如虎添翼。

本文Github地址

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

Javascript 相关文章推荐
推荐11款jQuery开发的复选框和单选框美化插件
Aug 02 Javascript
js字符串转换成xml对象并使用技巧解读
Apr 18 Javascript
登陆成功后自动计算秒数执行跳转
Jan 23 Javascript
jquery用offset()方法获得元素的xy坐标
Sep 06 Javascript
js简单判断flash是否加载完成的方法
Jun 21 Javascript
详细谈谈AngularJS的子级作用域问题
Sep 05 Javascript
微信小程序 九宫格实例代码
Jan 21 Javascript
JavaScript实现计算多边形质心的方法示例
Jan 31 Javascript
vue实现动态添加数据滚动条自动滚动到底部的示例代码
Jul 06 Javascript
Django模板继承 extend标签实例代码详解
May 16 Javascript
VUE 直接通过JS 修改html对象的值导致没有更新到数据中解决方法分析
Dec 02 Javascript
使用eslint和githooks统一前端风格的技巧
Jul 29 Javascript
vue组件学习教程
Sep 09 #Javascript
weex里Vuex state使用storage持久化详解
Sep 09 #Javascript
Vue2几种常见开局方式详解
Sep 09 #Javascript
使用JS动态显示文本
Sep 09 #Javascript
深入理解ES7的async/await的用法
Sep 09 #Javascript
jQuery事件对象的属性和方法详解
Sep 09 #jQuery
微信通过页面(H5)直接打开本地app的解决方法
Sep 09 #Javascript
You might like
PHP array_flip() 删除重复数组元素专用函数
2010/05/16 PHP
简单的php数据库操作类代码(增,删,改,查)
2013/04/08 PHP
php中如何同时使用session和cookie来保存用户登录信息
2013/07/05 PHP
浅析linux下apache服务器的配置和管理
2013/08/10 PHP
在PHP中使用X-SendFile头让文件下载更快
2014/06/01 PHP
php基本函数汇总
2015/07/09 PHP
Laravel 5.1 on SAE环境开发教程【附项目demo源码】
2016/10/09 PHP
实例讲解PHP表单
2020/06/10 PHP
有关PHP 中 config.m4 的探索
2020/08/26 PHP
ExtJS TabPanel beforeremove beforeclose使用说明
2010/03/31 Javascript
基于jquery的tab切换 js原理
2010/04/01 Javascript
javascript采用数组实现tab菜单切换效果
2012/12/12 Javascript
js传参数受特殊字符影响错误的解决方法
2013/10/21 Javascript
js控制文本框只输入数字和小数点的方法
2015/03/10 Javascript
jQuery在页面加载时动态修改图片尺寸的方法
2015/03/20 Javascript
jQuery子窗体取得父窗体元素的方法
2015/05/11 Javascript
js调用屏幕宽度的简单方法
2016/11/14 Javascript
Vue实现根据hash高亮选项卡
2019/05/27 Javascript
微信小程序在text文本实现多种字体样式
2019/11/08 Javascript
简介JavaScript错误处理机制
2020/08/04 Javascript
python3解析库BeautifulSoup4的安装配置与基本用法
2018/06/26 Python
python实现图片识别汽车功能
2018/11/30 Python
Python3.6.x中内置函数总结及讲解
2019/02/22 Python
python使用MQTT给硬件传输图片的实现方法
2019/05/05 Python
python日期与时间戳的各种转换示例
2020/02/12 Python
python实现时间序列自相关图(acf)、偏自相关图(pacf)教程
2020/06/03 Python
一文弄懂Pytorch的DataLoader, DataSet, Sampler之间的关系
2020/07/03 Python
福克斯租车:Fox Rent A Car
2017/04/13 全球购物
番木瓜健康和保健产品第一大制造商:Herbal Papaya
2017/04/25 全球购物
ellesse美国官方商店:意大利高级运动服品牌
2019/10/29 全球购物
俄罗斯一家时尚女装商店:Charuel
2019/12/04 全球购物
《天安门广场》教学反思
2014/04/23 职场文书
计划生育证明格式范本
2014/09/12 职场文书
Nginx访问日志及错误日志参数说明
2021/03/31 Servers
试了下Golang实现try catch的方法
2021/07/01 Golang
Windows环境下实现批量执行Sql文件
2021/10/05 SQL Server