1、导言

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

Expressnode 环境中非常流行的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地址

Express异步进化史的更多相关文章

  1. 【转】JavaScript 异步进化史

    前言 JS 中最基础的异步调用方式是 callback,它将回调函数 callback 传给异步 API,由浏览器或 Node 在异步完成后,通知 JS 引擎调用 callback.对于简单的异步操作 ...

  2. JavaScript 异步进化史

    前言 JS 中最基础的异步调用方式是 callback,它将回调函数 callback 传给异步 API,由浏览器或 Node 在异步完成后,通知 JS 引擎调用 callback.对于简单的异步操作 ...

  3. NodeJs03 express框架 Todo商城

    前言 由于NodeJs本身的异步非阻塞特性和对http的天然支持,所以使用NodeJs编写高性能,可伸缩的Web服务器非常简单.开发完整的Web服务器还需要路由,错误处理,请求拦截,请求和响应的解析, ...

  4. 写给Java开发者的Node.JS简介

    前言 今天上推特看见这篇文章,点进去发现是新货. 正好最近想入Node的坑,又有一些Java基础,所以希望翻译出来给大家,同时也让自己加深理解. 才疏学浅,如有不妥之处请指正. 原文链接:Node f ...

  5. express学习点滴- 永远不要忘记异步

    直接上两段代码,因为nodejs基于异步和事件回调的解决方式,涉及到异步的时候,问题往往藏得很深,以下这个简单的问题困扰了很久.之前怀疑是各种问题,到处改.直到最后一步一步跟代码,跟操作数据库部分豁然 ...

  6. Node.js实现RESTful api,express or koa?

    文章导读: 一.what's RESTful API 二.Express RESTful API 三.KOA RESTful API 四.express还是koa? 五.参考资料 一.what's R ...

  7. 【原】小玩node+express爬虫-2

    上周写了一个node+experss的爬虫小入门.今天继续来学习一下,写一个爬虫2.0版本. 这次我们不再爬博客园了,咋玩点新的,爬爬电影天堂.因为每个周末都会在电影天堂下载一部电影来看看. talk ...

  8. 【原】小玩node+express爬虫-1

    最近开始重新学习node.js,之前学的都忘了.所以准备重新学一下,那么,先从一个简单的爬虫开始吧. 什么是爬虫 百度百科的解释: 爬虫即网络爬虫,是一种自动获取网页内容的程序.是搜索引擎的重要组成部 ...

  9. express源码剖析1

    在通读源码之前,先把一些比较难理解的代码吃透: 1.EventEmitter.prototype mixin(app, EventEmitter.prototype, false); app为一个函数 ...

随机推荐

  1. DDD理论学习系列(12)-- 仓储

    DDD理论学习系列--案例及目录 1. 引言 DDD中Repository这个单词,主要有两种翻译:资源库和仓储,本文取仓储之译. 说到仓储,我们肯定就想到了仓库,仓库一般用来存放货物,而仓库一般由仓 ...

  2. python函数(4):递归函数及二分查找算法

    人理解循环,神理解递归!  一.递归的定义 def story(): s = """ 从前有个山,山里有座庙,庙里老和尚讲故事, 讲的什么呢? ""& ...

  3. 微信小程序开发基础知识总结

    微信小程序在无论在功能.文档及相关支持方面,都是优于前面几种微信账号类型,它提供了很多原生程序才有的接口,使得我们的小程序在很多方面突破H5页面应用的限制,更加接近原生程序的功能,因此微信小程序具有很 ...

  4. Python网络数据采集6-隐含输入字段

    Python网络数据采集6-隐含输入字段 selenium的get_cookies可以轻松获取所有cookie. from pprint import pprint from selenium imp ...

  5. SSH项目过一段时间之后再访问会报一次Could not open Hibernate session for transaction 异常,Caused by: com.mysql.jdbc.CommunicationsException: Communications link failure due to underlyi,再重新方法即可访问成功(通常出现在过了一晚之后再去访问系统)

    前端时间到客户那去进行项目的上线测试,将项目部署好之后,运行都是正常的,可是每到了第二天早上访问的时候,就会报一个Could not open Hibernate session for transa ...

  6. Java中基本数据类型和包装类

    参考:深入剖析Java中的装箱和拆箱; Java中基本数据类型和包装类互转中 缓冲机制的使用; java学习笔记:装箱和拆箱,包装器和缓冲池 Java 各 类型数据在内存中分配情况详解 一 java内 ...

  7. cacti监控部署与配置

    cacti是一套基于PHP,mysql,SNMP及RRDTool开发的网络流量测试图形分析工具 cacti是通过snmpget来获取数据,使用RRDtool绘画图形 ,而且完全可以不需要了解RRDto ...

  8. golang的闭包和普通函数调用区别

    先看一段程序 package main import ( "fmt") func main() {  a := []int{1, 2, 3}  for _, i := range ...

  9. 一步一步学多线程-Timer

    在执行定时任务的时候可以用Timer来实现,现在小编对学到的进行一次总结,先来看一个demo 1 public class TimerTest { 2 3 public static void mai ...

  10. 程序设计中的数学思维函数总结(代码以C#为例)

    最近以C#为例,学习了程序设计基础,其中涉及到一些数学思维,我们可以巧妙的将这些逻辑问题转换为代码,交给计算机运算. 现将经常会使用到的基础函数做一总结,供大家分享.自己备用. 1.判断一个数是否为奇 ...