在 Javascript
的世界裏,異步(因爲JavaScript的單線程運行,因此JavaScript中的異步是能夠阻塞的)無處不在。javascript
Express 是 node
環境中很是流行的Web服務端框架,有很大比例的 Node Web應用
採用了 Express
。java
當使用 JavaScript
編寫服務端代碼時,咱們無可避免的會大量使用到異步。隨着 JavaScript、Node
的進化,咱們的異步處理方式,也就隨之進化。node
接下來,咱們就來看看 Express
中異步處理的進化過程。git
在異步的世界裏,咱們須要想辦法獲取的異步方法完畢的通知,那在 JavaScript
中,會有哪些方式呢?github
回調是 JS
中最原始,也是最古老的異步通知機制。express
function asyncFn(callback) { // 利用setTimeout模擬異步 setTimeout(function () { console.log('執行完畢'); callback(); // 發通知 }, 2000); } asyncFn(function () { console.log('我會在2s後輸出'); });
要獲取結果的函數,監聽某個時間。在異步方法完成後,觸發該事件,達到通知的效果。編程
經過觀察者模式,在異步完成時,修改發佈者。這個時候,發佈者會把變動通知到訂閱者。promise
Promise
是回調函數的改進。使用它, 咱們能夠將異步平行化,避免回調地獄。框架
function asyncFn() { return new Promise((resolve, reject) => { // 利用setTimeout模擬異步 setTimeout(function () { console.log('執行完畢'); resolve(); // 發通知(是否有感受到回調的影子?) }, 2000); }); } asyncFn() .then(function () { console.log('我會在2s後輸出'); });
Generator 函數是 ES6 提供的一種異步編程解決方案。koa
如下代碼只是簡單演示,實際上 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後輸出'); });
能夠說是當前 JavaScript
中,處理異步的最佳方案。
function asyncFn() { return new Promise((resolve, reject) => { // 利用setTimeout模擬異步 setTimeout(function () { console.log('執行完畢'); resolve(); // 發通知(是否有感受到回調的影子?) }, 2000); }); } async function run(){ await asyncFn(); console.log('我會在2s後輸出'); } run();
在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;
在 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;
該方案中,將多個業務邏輯,包裝爲返回 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;
實際上,該方案也是須要 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;
雖然 koa
對更新、更好的用法(koa是generator,koa2原生async)支持的更好。但做爲從 node 0.x
開始跟的我,對 Express
仍是有特殊的好感。
以上的一些方案,已經與 koa
中使用無異,配合 Express
龐大的生態圈,無異於如虎添翼。