Egg.js 與 Koa

2020-02-06 10:54 更新

異步編程模型

Node.js 是一個(gè)異步的世界,官方 API 支持的都是 callback 形式的異步編程模型,這會(huì)帶來(lái)許多問(wèn)題,例如

  • callback hell: 最臭名昭著的 callback 嵌套問(wèn)題。
  • release zalgo: 異步函數(shù)中可能同步調(diào)用 callback 返回?cái)?shù)據(jù),帶來(lái)不一致性。

因此社區(qū)提供了各種異步的解決方案,最終勝出的是 Promise,它也內(nèi)置到了 ECMAScript 2015 中。而在 Promise 的基礎(chǔ)上,結(jié)合 Generator 提供的切換上下文能力,出現(xiàn)了 co 等第三方類庫(kù)來(lái)讓我們用同步寫(xiě)法編寫(xiě)異步代碼。同時(shí),async function 這個(gè)官方解決方案也于 ECMAScript 2017 中發(fā)布,并在 Node.js 8 中實(shí)現(xiàn)。

async function

async function 是語(yǔ)言層面提供的語(yǔ)法糖,在 async function 中,我們可以通過(guò) await 關(guān)鍵字來(lái)等待一個(gè) Promise 被 resolve(或者 reject,此時(shí)會(huì)拋出異常), Node.js 現(xiàn)在的 LTS 版本(8.x)已原生支持。

const fn = async function() {
const user = await getUser();
const posts = await fetchPosts(user.id);
return { user, posts };
};
fn().then(res => console.log(res)).catch(err => console.error(err.stack));

Koa

Koa 是一個(gè)新的 web 框架,由 Express 幕后的原班人馬打造, 致力于成為 web 應(yīng)用和 API 開(kāi)發(fā)領(lǐng)域中的一個(gè)更小、更富有表現(xiàn)力、更健壯的基石。

Koa 和 Express 的設(shè)計(jì)風(fēng)格非常類似,底層也都是共用的同一套 HTTP 基礎(chǔ)庫(kù),但是有幾個(gè)顯著的區(qū)別,除了上面提到的默認(rèn)異步解決方案之外,主要的特點(diǎn)還有下面幾個(gè)。

Middleware

Koa 的中間件和 Express 不同,Koa 選擇了洋蔥圈模型。

  • 中間件洋蔥圖:

  • 中間件執(zhí)行順序圖:

所有的請(qǐng)求經(jīng)過(guò)一個(gè)中間件的時(shí)候都會(huì)執(zhí)行兩次,對(duì)比 Express 形式的中間件,Koa 的模型可以非常方便的實(shí)現(xiàn)后置處理邏輯,對(duì)比 Koa 和 Express 的 Compress 中間件就可以明顯的感受到 Koa 中間件模型的優(yōu)勢(shì)。

Context

和 Express 只有 Request 和 Response 兩個(gè)對(duì)象不同,Koa 增加了一個(gè) Context 的對(duì)象,作為這次請(qǐng)求的上下文對(duì)象(在 Koa 1 中為中間件的 this,在 Koa 2 中作為中間件的第一個(gè)參數(shù)傳入)。我們可以將一次請(qǐng)求相關(guān)的上下文都掛載到這個(gè)對(duì)象上。類似 traceId 這種需要貫穿整個(gè)請(qǐng)求(在后續(xù)任何一個(gè)地方進(jìn)行其他調(diào)用都需要用到)的屬性就可以掛載上去。相較于 request 和 response 而言更加符合語(yǔ)義。

同時(shí) Context 上也掛載了 Request 和 Response 兩個(gè)對(duì)象。和 Express 類似,這兩個(gè)對(duì)象都提供了大量的便捷方法輔助開(kāi)發(fā),例如

  • get request.query
  • get request.hostname
  • set response.body
  • set response.status

異常處理

通過(guò)同步方式編寫(xiě)異步代碼帶來(lái)的另外一個(gè)非常大的好處就是異常處理非常自然,使用 try catch 就可以將按照規(guī)范編寫(xiě)的代碼中的所有錯(cuò)誤都捕獲到。這樣我們可以很便捷的編寫(xiě)一個(gè)自定義的錯(cuò)誤處理中間件。

async function onerror(ctx, next) {
try {
await next();
} catch (err) {
ctx.app.emit('error', err);
ctx.body = 'server error';
ctx.status = err.status || 500;
}
}

只需要將這個(gè)中間件放在其他中間件之前,就可以捕獲它們所有的同步或者異步代碼中拋出的異常了。

Egg 繼承于 Koa

如上述,Koa 是一個(gè)非常優(yōu)秀的框架,然而對(duì)于企業(yè)級(jí)應(yīng)用來(lái)說(shuō),它還比較基礎(chǔ)。

而 Egg 選擇了 Koa 作為其基礎(chǔ)框架,在它的模型基礎(chǔ)上,進(jìn)一步對(duì)它進(jìn)行了一些增強(qiáng)。

擴(kuò)展

在基于 Egg 的框架或者應(yīng)用中,我們可以通過(guò)定義 app/extend/{application,context,request,response}.js 來(lái)擴(kuò)展 Koa 中對(duì)應(yīng)的四個(gè)對(duì)象的原型,通過(guò)這個(gè)功能,我們可以快速的增加更多的輔助方法,例如我們?cè)?nbsp;app/extend/context.js 中寫(xiě)入下列代碼:

// app/extend/context.js
module.exports = {
get isIOS() {
const iosReg = /iphone|ipad|ipod/i;
return iosReg.test(this.get('user-agent'));
},
};

在 Controller 中,我們就可以使用到剛才定義的這個(gè)便捷屬性了:

// app/controller/home.js
exports.handler = ctx => {
ctx.body = ctx.isIOS
? 'Your operating system is iOS.'
: 'Your operating system is not iOS.';
};

更多關(guān)于擴(kuò)展的內(nèi)容,請(qǐng)查看擴(kuò)展章節(jié)。

插件

眾所周知,在 Express 和 Koa 中,經(jīng)常會(huì)引入許許多多的中間件來(lái)提供各種各樣的功能,例如引入 koa-session 提供 Session 的支持,引入 koa-bodyparser 來(lái)解析請(qǐng)求 body。而 Egg 提供了一個(gè)更加強(qiáng)大的插件機(jī)制,讓這些獨(dú)立領(lǐng)域的功能模塊可以更加容易編寫(xiě)。

一個(gè)插件可以包含

  • extend:擴(kuò)展基礎(chǔ)對(duì)象的上下文,提供各種工具類、屬性。
  • middleware:增加一個(gè)或多個(gè)中間件,提供請(qǐng)求的前置、后置處理邏輯。
  • config:配置各個(gè)環(huán)境下插件自身的默認(rèn)配置項(xiàng)。

一個(gè)獨(dú)立領(lǐng)域下的插件實(shí)現(xiàn),可以在代碼維護(hù)性非常高的情況下實(shí)現(xiàn)非常完善的功能,而插件也支持配置各個(gè)環(huán)境下的默認(rèn)(最佳)配置,讓我們使用插件的時(shí)候幾乎可以不需要修改配置項(xiàng)。

egg-security 插件就是一個(gè)典型的例子。

更多關(guān)于插件的內(nèi)容,請(qǐng)查看插件章節(jié)。

Egg 與 Koa 的版本關(guān)系

Egg 1.x

Egg 1.x 發(fā)布時(shí),Node.js 的 LTS 版本尚不支持 async function,所以 Egg 1.x 仍然基于 Koa 1.x 開(kāi)發(fā),但是在此基礎(chǔ)上,Egg 全面增加了 async function 的支持,再加上 Egg 對(duì) Koa 2.x 的中間件也完全兼容,應(yīng)用層代碼可以完全基于 async function 來(lái)開(kāi)發(fā)。

  • 底層基于 Koa 1.x,異步解決方案基于 co 封裝的 generator function。
  • 官方插件以及 Egg 核心使用 generator function 編寫(xiě),保持對(duì) Node.js LTS 版本的支持,在必要處通過(guò) co 包裝以兼容在 async function 中的使用。
  • 應(yīng)用開(kāi)發(fā)者可以選擇 async function(Node.js 8.x+) 或者 generator function(Node.js 6.x+)進(jìn)行編寫(xiě)。

Egg 2.x

Node.js 8 正式進(jìn)入 LTS 后,async function 可以在 Node.js 中使用并且沒(méi)有任何性能問(wèn)題了,Egg 2.x 基于 Koa 2.x,框架底層以及所有內(nèi)置插件都使用 async function 編寫(xiě),并保持了對(duì) Egg 1.x 以及 generator function 的完全兼容,應(yīng)用層只需要升級(jí)到 Node.js 8 即可從 Egg 1.x 遷移到 Egg 2.x。

  • 底層基于 Koa 2.x,異步解決方案基于 async function。
  • 官方插件以及 Egg 核心使用 async function 編寫(xiě)。
  • 建議業(yè)務(wù)層遷移到 async function 方案。
  • 只支持 Node.js 8 及以上的版本。


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)