淺談Express異步進化史
1、導言
在 Javascript 的世界里,異步(由于JavaScript的單線程運行,所以JavaScript中的異步是可以阻塞的)無處不在。
Express 是 node 環(huán)境中非常流行的Web服務(wù)端框架,有很大比例的 Node Web應(yīng)用 采用了 Express。
當使用 JavaScript 編寫服務(wù)端代碼時,我們無可避免的會大量使用到異步。隨著 JavaScript、Node 的進化,我們的異步處理方式,也就隨之進化。
接下來,我們就來看看 Express 中異步處理的進化過程。
2、JavaScript的異步處理
在異步的世界里,我們需要想辦法獲取的異步方法完畢的通知,那在 JavaScript 中,會有哪些方式呢?
2.1、回調(diào)
回調(diào)是 JS 中最原始,也是最古老的異步通知機制。
function asyncFn(callback) {
// 利用setTimeout模擬異步
setTimeout(function () {
console.log('執(zhí)行完畢');
callback(); // 發(fā)通知
}, 2000);
}
asyncFn(function () {
console.log('我會在2s后輸出');
});
2.2、事件監(jiān)聽
要獲取結(jié)果的函數(shù),監(jiān)聽某個時間。在異步方法完成后,觸發(fā)該事件,達到通知的效果。
2.3、發(fā)布/訂閱
通過觀察者模式,在異步完成時,修改發(fā)布者。這個時候,發(fā)布者會把變更通知到訂閱者。
2.4、Promise
Promise 是回調(diào)函數(shù)的改進。使用它, 我們可以將異步平行化,避免回調(diào)地獄。
function asyncFn() {
return new Promise((resolve, reject) => {
// 利用setTimeout模擬異步
setTimeout(function () {
console.log('執(zhí)行完畢');
resolve(); // 發(fā)通知(是否有感覺到回調(diào)的影子?)
}, 2000);
});
}
asyncFn()
.then(function () {
console.log('我會在2s后輸出');
});
2.5、生成器(Generator)
Generator 函數(shù)是 ES6 提供的一種異步編程解決方案。
以下代碼只是簡單演示,實際上 Generator 的使用過程,相對是比較復雜的,這是另外一個話題,本文暫且不表。
function asyncFn() {
return new Promise((resolve, reject) => {
// 利用setTimeout模擬異步
setTimeout(function () {
console.log('執(zhí)行完畢');
resolve(); // 發(fā)通知(是否有感覺到回調(diào)的影子?)
}, 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('執(zhí)行完畢');
resolve(); // 發(fā)通知(是否有感覺到回調(diào)的影子?)
}, 2000);
});
}
async function run(){
await asyncFn();
console.log('我會在2s后輸出');
}
run();
3、Express中的異步處理
在Express中,我們一般常用的是方案是:回調(diào)函數(shù)、Promise、以及async...await。
為了搭建演示環(huán)境,通過 express-generator 初始化一個express項目。一般的服務(wù)端項目,都是路由調(diào)用業(yè)務(wù)邏輯。所以,我們也遵循這個原則:
打開 routs/index.js,我們會看到如下內(nèi)容,以下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、回調(diào)函數(shù)處理Express異步邏輯
在 Express 中,路由可以加載多個中間件,所以我們可以把業(yè)務(wù)邏輯按照中間件的寫法進行編寫。這樣通過一層層的next,就能非常方便的拆分異步邏輯。
var express = require('express');
var router = express.Router();
function asyncFn(req, res, next) {
setTimeout(() => {
req.user = {}; // 設(shè)置當前請求的用戶
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異步邏輯
該方案中,將多個業(yè)務(wù)邏輯,包裝為返回 Promise 的函數(shù)。通過業(yè)務(wù)方法進行組合調(diào)用,以達到一進一出的效果。
var express = require('express');
var router = express.Router();
function asyncFn(req, res) {
return new Promise((resolve, reject) => {
setTimeout(() => {
req.user = {}; // 設(shè)置當前請求的用戶
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異步邏輯
實際上,該方案也是需要 Promise 的支持,只是寫法上,更直觀,錯誤處理也更直接。
需要注意的是,Express是早期的方案,沒有對async...await進行全局錯誤處理,所以可以采用包裝方式,進行處理。
var express = require('express');
var router = express.Router();
function asyncFn(req) {
return new Promise((resolve, reject) => {
setTimeout(() => {
req.user = {}; // 設(shè)置當前請求的用戶
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中的錯誤處理
}
}
};
/* GET home page. */
router.get('/', tools.asyncWrap(doBizAsync)); // 需要用工具方法包裹一下
module.exports = router;
4、總結(jié)
雖然 koa 對更新、更好的用法(koa是generator,koa2原生async)支持的更好。但作為從 node 0.x 開始跟的我,對 Express 還是有特殊的好感。
以上的一些方案,已經(jīng)與 koa 中使用無異,配合 Express 龐大的生態(tài)圈,無異于如虎添翼。
本文Github地址
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Node 搭建一個靜態(tài)資源服務(wù)器的實現(xiàn)
這篇文章主要介紹了Node 搭建一個靜態(tài)資源服務(wù)器的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2019-05-05
node異步使用await和不用await的區(qū)別實例分析
這篇文章主要介紹了node異步使用await和不用await的區(qū)別,結(jié)合實例形式分析了node.js異步使用await和不用await的實例中,同步與異步執(zhí)行的區(qū)別,需要的朋友可以參考下2023-06-06
node+koa2+mysql+bootstrap搭建一個前端論壇
本篇文章通過實例給大家分享了用node+koa2+mysql+bootstrap搭建一個前端論壇的步驟,有需要的朋友參考下。2018-05-05
node.js中fs.stat與fs.fstat的區(qū)別詳解
fs.stat和fs.fstat他們都是用來獲取文件的狀態(tài)信息,下面這篇文章主要給大家介紹了關(guān)于node.js中fs.stat與fs.fstat區(qū)別的相關(guān)資料,文中介紹的非常詳細,對大家具有一定的參考學習價值,需要的朋友們下面來一起看看吧。2017-06-06
Nodejs處理Json文件并將處理后的數(shù)據(jù)寫入新文件中
這篇文章主要介紹了Nodejs處理Json文件并將處理后的數(shù)據(jù)寫入新文件中,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-10-10

