很多時候,我經常會看到,當人們推薦 NodeJS 服務端框架的時候,總是會說「用 Koa 啊,它可以用 async/await」。我不是說對這個框架自稱「下一代 web 框架」有什麼意見,實際上,大多數框架都說自己是「下一代」,但是很多讀者不知道的是,Koa 提出這句話的時候,那大該是兩年前了吧,JavaScript 現在發展是日新月異,兩年前的「下一代」放在現在,也早已成為了「上一代」(指的不僅是 Koa)。但這篇文章裏,我不打算糾結什麼是下一代的問題,我想說說總是和 Koa 框架「捆綁」在一起的 async/await 到底是怎麼回事。

不過別誤會,我不是要講 async/await 的歷史由來,只是要講它們的應用場景。如果想了解它們的歷史由來,可以自行百科(async/await 不是 JavaScript 獨有的語法,很多語言都有)。首先,我想展示兩個 Koa 和 Express 框架的示例,對比一下它們處理非同步邏輯時有什麼區別。

// Koa
import Axios from "axios";
import * as Koa from "koa";

var app = new Koa();

app.use(async (ctx, next) => {
let { data } = await Axios.get(/* something remote */);
ctx.body = data;

next(); // 如果 next() 在後面調用,await 是 沒必要的
});

上面的例子很簡單,基本上所有人都看的懂,如果是這樣,那麼下面的例子,很多人就會有疑問了。

// Express
import Axios from "axios";
import * as express from "express";

var app = express();

app.get("/", async (req, res, next) => {
let { data } = await Axios.get(/* something remote */);
res.send(data);

next(); // 因為響應已經發送,所以其實這個 next() 也沒多大意義,只用來執行一些後置邏輯
});

是不是很酷呢?在 Express 框架中使用 async/await。這樣真的可以嗎?我可以很肯定地告訴你,是的。「這都2019年了,誰還在嵌套回調函數啊。」「在我們的項目中,到處充斥著 async/await。」如果你已經識得其中奧妙,那麼接下來的內容就無關緊要了。但如果你還對此有疑問,那麼請繼續看下面的解釋。

要理解這兩份代碼,只需要理解兩個事實:

(1)Koa 框架和 Express 框架一樣,會在 next() 調用的時候,執行下一個中間件函數。

(2)async 函數和普通函數沒有多大的不同,你可以把它們當作幾乎任何場景的回調函數。

所以,只要你還沒有調用 next(),你就可以在前面執行任何你期望的代碼,無論它是同步的,還是非同步的。async 唯一的作用是,讓你可以使用 await。如果你還不能理解,那麼下面的 SocketIO 示例可能會讓你印象更為深刻。

// SocketIO
import Axios from "axios";
import * as SocketIO from "socket.io";

var io = SocketIO();

io.use(async (socket, next) => {
// 在調用 next() 前使用 await 做一些非同步的事情

next();
});

io.on("connection", (socket) => {
socket.on("greeting", async () => {
let { data } = await Axios.get(/* something remote */);
socket.emit("greeting-back", data);
});
});

正如我上面所說的,你可以把 async 函數當成任何場景的回調函數,因此你甚至可以直接將一個 async 函數綁定在 EventEmitter 上(實際上上面的 SocketIO 例子已經這麼做了)。不過需要注意的是,對於純粹的 EventEmitter 實例,它們沒有 next() 函數供你選擇,因此所有綁定的回調函數都是一次性執行的,不會等待上一個非同步函數執行完成。

你可能想要問,既然如此,那麼為什麼 Koa 框架要設計繼續支持 next 函數呢?直接設計為等待上一個 async 函數執行完成就可以了啊。這個問題,也是我的一個疑問,我覺得這可能有兩個原因:一是和 Express 盡量保持相似的使用體驗(框架中的一大堆方法都這麼做),二是這樣它可以實現在當前函數執行完成之前提前執行下一個回調函數(我不是很懂這樣做的意義是什麼)。

最後,如果你想要問我更支持哪個框架,很顯然的,我更支持 Express。一是因為它的社區插件支持更豐富,當然最重要的是它邏輯上更簡單,它沒有 Koa 那種說先執行下一個中間件再繼續執行當前中間件的說法,所有的中間件都是順序執行的,很容易理解。

當然實際上我自己是不使用 Express 框架的(不滿足需求),我使用的是自己編寫的 webium 框架,它同時具有 Express 和 Koa 的風格,也支持絕大多數 Express 的中間件,同時它支持 NodeJS 內置的 HTTP2 模塊(Express 不支持,Koa 可能支持),另外 webium 中間件的 next 是可選的,如果傳入的是一個 async 函數,則可以不需要 next(),框架會智能地處理。另外它還支持路由熱重載功能,這樣就不必總是需要重啟伺服器才載入新的邏輯。

hyurl/webium?

github.com圖標
推薦閱讀:

相關文章