淺談Express異步進(jìn)化史
1、導(dǎo)言
在 Javascript 的世界里,異步(由于JavaScript的單線程運(yùn)行,所以JavaScript中的異步是可以阻塞的)無(wú)處不在。
Express 是 node 環(huán)境中非常流行的Web服務(wù)端框架,有很大比例的 Node Web應(yīng)用 采用了 Express。
當(dāng)使用 JavaScript 編寫(xiě)服務(wù)端代碼時(shí),我們無(wú)可避免的會(huì)大量使用到異步。隨著 JavaScript、Node 的進(jìn)化,我們的異步處理方式,也就隨之進(jìn)化。
接下來(lái),我們就來(lái)看看 Express 中異步處理的進(jìn)化過(guò)程。
2、JavaScript的異步處理
在異步的世界里,我們需要想辦法獲取的異步方法完畢的通知,那在 JavaScript 中,會(huì)有哪些方式呢?
2.1、回調(diào)
回調(diào)是 JS 中最原始,也是最古老的異步通知機(jī)制。
function asyncFn(callback) { // 利用setTimeout模擬異步 setTimeout(function () { console.log('執(zhí)行完畢'); callback(); // 發(fā)通知 }, 2000); } asyncFn(function () { console.log('我會(huì)在2s后輸出'); });
2.2、事件監(jiān)聽(tīng)
要獲取結(jié)果的函數(shù),監(jiān)聽(tīng)某個(gè)時(shí)間。在異步方法完成后,觸發(fā)該事件,達(dá)到通知的效果。
2.3、發(fā)布/訂閱
通過(guò)觀察者模式,在異步完成時(shí),修改發(fā)布者。這個(gè)時(shí)候,發(fā)布者會(huì)把變更通知到訂閱者。
2.4、Promise
Promise 是回調(diào)函數(shù)的改進(jìn)。使用它, 我們可以將異步平行化,避免回調(diào)地獄。
function asyncFn() { return new Promise((resolve, reject) => { // 利用setTimeout模擬異步 setTimeout(function () { console.log('執(zhí)行完畢'); resolve(); // 發(fā)通知(是否有感覺(jué)到回調(diào)的影子?) }, 2000); }); } asyncFn() .then(function () { console.log('我會(huì)在2s后輸出'); });
2.5、生成器(Generator)
Generator 函數(shù)是 ES6 提供的一種異步編程解決方案。
以下代碼只是簡(jiǎn)單演示,實(shí)際上 Generator 的使用過(guò)程,相對(duì)是比較復(fù)雜的,這是另外一個(gè)話題,本文暫且不表。
function asyncFn() { return new Promise((resolve, reject) => { // 利用setTimeout模擬異步 setTimeout(function () { console.log('執(zhí)行完畢'); resolve(); // 發(fā)通知(是否有感覺(jué)到回調(diào)的影子?) }, 2000); }); } function* generatorSync() { var result = yield asyncFn(); } var g = generatorSync(); g.next().value.then(()=>{ console.log('我會(huì)在2s后輸出'); });
2.6、async...await
可以說(shuō)是當(dāng)前 JavaScript 中,處理異步的最佳方案。
function asyncFn() { return new Promise((resolve, reject) => { // 利用setTimeout模擬異步 setTimeout(function () { console.log('執(zhí)行完畢'); resolve(); // 發(fā)通知(是否有感覺(jué)到回調(diào)的影子?) }, 2000); }); } async function run(){ await asyncFn(); console.log('我會(huì)在2s后輸出'); } run();
3、Express中的異步處理
在Express中,我們一般常用的是方案是:回調(diào)函數(shù)、Promise、以及async...await。
為了搭建演示環(huán)境,通過(guò) express-generator 初始化一個(gè)express項(xiàng)目。一般的服務(wù)端項(xiàng)目,都是路由調(diào)用業(yè)務(wù)邏輯。所以,我們也遵循這個(gè)原則:
打開(kāi) routs/index.js,我們會(huì)看到如下內(nèi)容,以下Demo就以此文件來(lái)做演示。
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、回調(diào)函數(shù)處理Express異步邏輯
在 Express 中,路由可以加載多個(gè)中間件,所以我們可以把業(yè)務(wù)邏輯按照中間件的寫(xiě)法進(jìn)行編寫(xiě)。這樣通過(guò)一層層的next,就能非常方便的拆分異步邏輯。
var express = require('express'); var router = express.Router(); function asyncFn(req, res, next) { setTimeout(() => { req.user = {}; // 設(shè)置當(dāng)前請(qǐng)求的用戶 next(); }, 2000); } function asyncFn2(req, res, next) { setTimeout(() => { req.auth = {}; // 設(shè)置用戶權(quán)限 next(); }, 2000); } function asyncFn3(req, res, next) { setTimeout(() => { res.locals = { title: 'Express Async Test' }; // 設(shè)置數(shù)據(jù) res.render('index'); // 響應(yīng) }, 2000); } /* GET home page. */ router.get('/', asyncFn, asyncFn2, asyncFn3); // 一步步執(zhí)行中間件 module.exports = router;
3.2、Promise 處理Express異步邏輯
該方案中,將多個(gè)業(yè)務(wù)邏輯,包裝為返回 Promise 的函數(shù)。通過(guò)業(yè)務(wù)方法進(jìn)行組合調(diào)用,以達(dá)到一進(jìn)一出的效果。
var express = require('express'); var router = express.Router(); function asyncFn(req, res) { return new Promise((resolve, reject) => { setTimeout(() => { req.user = {}; // 設(shè)置當(dāng)前請(qǐng)求的用戶 resolve(req); }, 2000); }); } function asyncFn2(req) { return new Promise((resolve, reject) => { setTimeout(() => { req.auth = {}; // 設(shè)置用戶權(quán)限 resolve(); }, 2000); }); } function asyncFn3(res) { return new Promise((resolve, reject) => { setTimeout(() => { res.locals = { title: 'Express Async Test' }; // 設(shè)置數(shù)據(jù) res.render('index'); // 響應(yīng) }, 2000); }); } function doBizAsync(req, res, next) { asyncFn(req) .then(() => asyncFn2(req)) .then(() => asyncFn3(res)) .catch(next); // 統(tǒng)一異常處理 }; /* GET home page. */ router.get('/', doBizAsync); module.exports = router;
3.3、async...await 處理Express異步邏輯
實(shí)際上,該方案也是需要 Promise 的支持,只是寫(xiě)法上,更直觀,錯(cuò)誤處理也更直接。
需要注意的是,Express是早期的方案,沒(méi)有對(duì)async...await進(jìn)行全局錯(cuò)誤處理,所以可以采用包裝方式,進(jìn)行處理。
var express = require('express'); var router = express.Router(); function asyncFn(req) { return new Promise((resolve, reject) => { setTimeout(() => { req.user = {}; // 設(shè)置當(dāng)前請(qǐng)求的用戶 resolve(req); }, 2000); }); } function asyncFn2(req) { return new Promise((resolve, reject) => { setTimeout(() => { req.auth = {}; // 設(shè)置用戶權(quán)限 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' }; // 設(shè)置數(shù)據(jù) res.render('index'); // 響應(yīng) }; const tools = { asyncWrap(fn) { return (req, res, next) => { fn(req, res, next).catch(next); // async...await在Express中的錯(cuò)誤處理 } } }; /* GET home page. */ router.get('/', tools.asyncWrap(doBizAsync)); // 需要用工具方法包裹一下 module.exports = router;
4、總結(jié)
雖然 koa 對(duì)更新、更好的用法(koa是generator,koa2原生async)支持的更好。但作為從 node 0.x 開(kāi)始跟的我,對(duì) Express 還是有特殊的好感。
以上的一些方案,已經(jīng)與 koa 中使用無(wú)異,配合 Express 龐大的生態(tài)圈,無(wú)異于如虎添翼。
本文Github地址
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
node.js中的http.request.end方法使用說(shuō)明
這篇文章主要介紹了node.js中的http.request.end方法使用說(shuō)明,本文介紹了http.request.end的方法說(shuō)明、語(yǔ)法、接收參數(shù)、使用實(shí)例和實(shí)現(xiàn)源碼,需要的朋友可以參考下2014-12-12windows系統(tǒng)下安裝yarn的詳細(xì)教程
yarn是一個(gè)新的JS包管理工具,它的出現(xiàn)是為了彌補(bǔ)npm的一些缺陷,下面這篇文章主要給大家介紹了關(guān)于windows系統(tǒng)下安裝yarn的詳細(xì)教程,文中通過(guò)圖文介紹的非常詳細(xì),需要的朋友可以參考下2023-02-02npm install --save 、--save-dev 、-D、-S&nb
這篇文章主要介紹了npm install --save 、--save-dev 、-D、-S 的區(qū)別與NODE_ENV的配置方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-08-08使用puppeteer破解極驗(yàn)的滑動(dòng)驗(yàn)證碼
這篇文章主要介紹了利用puppeteer破解極驗(yàn)的滑動(dòng)驗(yàn)證功能,基本流程代碼實(shí)現(xiàn)給大家介紹的非常詳細(xì),需要的朋友可以參考下2018-02-02從零開(kāi)始學(xué)習(xí)Node.js系列教程之設(shè)置HTTP頭的方法示例
這篇文章主要介紹了Node.js設(shè)置HTTP頭的方法,詳細(xì)分析了常見(jiàn)HTTP頭的功能、原理及相關(guān)設(shè)置操作技巧,需要的朋友可以參考下2017-04-04如何用Node.js編寫(xiě)內(nèi)存效率高的應(yīng)用程序
這篇文章主要介紹了如何用Node.js編寫(xiě)內(nèi)存效率高的應(yīng)用程序,對(duì)Node.js感興趣的同學(xué),可以參考下2021-04-04