上圖是一個典型的採用 Node.js 開發 web 應用的先後端結構,下面介紹一下 Node 服務層在其中的做用以及使用 Node.js 的一些優劣。前端
Node 服務層做用:node
請求代理 傳統作法是後端提供 api 供前端直接調用,但後端逐漸趨於服務化,直接調用面臨的問題有:web
跨域ajax
數據須要二次加工express
後端服務部署在內網時,前端沒法直接調用json
路由後端
模板渲染api
使用 Node.js 的優點:跨域
先後端分離,節省溝通、聯調成本。promise
生態圈繁榮,第三方模塊不少,合理使用能夠大量提高開發效率。
處理高併發場景性能更高,適合 web 應用。
使用 Node.js 的劣勢:
js 是弱類型語言,可靠性不高,潛在問題很難發現。
不適合 CPU 密集型的應用,如視頻編解碼。
提到 Node.js 開發,不得不提目前煊赫一時的2大框架 Express 和 Koa。
Express 誕生已有時日, 是一個基於 Node.js 平臺的極簡、靈活的 web 應用開發框架,主要基於 Connect 中間件,而且自身封裝了路由、視圖處理等功能,使用人數衆多。
Koa 相對更爲年輕, 是 Express 原班人馬基於 ES6 新特性從新開發的框架,主要基於 co 中間件,框架自身不包含任何中間件,不少功能須要藉助第三方中間件解決,可是因爲其基於 ES6 generator 特性的異步流程控制,解決了 "callback hell" 和麻煩的錯誤處理問題,大受開發者歡迎。Koa 目前正式版本是 1.2.4,Koa 團隊表示 2.0 版本須要 node 支持 async/await 時纔會發佈。
二者建立一個基礎的 Web 服務都很是簡單,寫法也基本相同,最大的區別是路由處理 Express 是自身集成的,而 Koa 須要引入中間件。
// Express var express = require('express') var app = express() app.get('/', function (req, res) { res.send('Hello World!') }) app.listen(3000)
// Koa var koa = require('koa') var route = require('koa-route') var app = koa() app.use(route.get('/', function *(){ this.body = 'Hello World' })) app.listen(3000)
Express 自身集成了視圖功能,提供了 consolidate.js 功能,支持幾乎全部 JavaScript 模板引擎,並提供了視圖設置的便利方法。 Koa 須要引入 co-views 中間件。
// Express var express = require('express') var app = express() app.set('views', __dirname + '/views') app.set('view engine', 'jade') app.get('/', function (req, res) { res.render('index', { title: 'bilibili' }) })
// Koa var koa = require('koa') var route = require('koa-route') var views = require('co-views') var render = views(__dirname + '/views', { default: "jade" }) var app = koa() app.use(route.get('/', function *() { this.body = yield render('index', { title: 'bilibili' }) }))
兩個框架都封裝了HTTP Request對象,有一點不一樣是 Koa v1 使用 this 取代 Express 的 req、res。
// Express var app = require('express')() app.get('/room/:id', function (req, res) { console.log(req.params) }) // 獲取POST數據須要 body-parser 中間件 var bodyParser = require('body-parser') app.use(bodyParser.json()) app.post('/sendgift', function (req, res) { console.log(req.body) })
// Koa var app = require('koa')() var route = require('koa-route') app.use(route.get('/room/:id', function *() { console.log(this.req.query) })) // 獲取POST數據須要 co-body 中間件 var parse = require('co-body') app.use(route.post('/sendgift', function *() { var post = yield parse(this.request) console.log(post) }))
Express 採用 callback 來處理異步,Koa v1 採用 generator,Koa v2 採用 async/await。
下面分別對 js 當中 callback、promise、generator、async/await 這四種異步流程控制進行了對比,
generator 和 async/await 使用同步的寫法來處理異步,明顯好於 callback 和 promise,async/await 在語義化上又要比 generator 更強。
// callback var api1 = 'https://anapioficeandfire.com/api/characters/583' var api2 = 'https://anapioficeandfire.com/api/characters/584' function fetchData () { $.ajax({ type: 'GET', url: api1, dataType: 'json', success: function (data1) { $.ajax({ type: 'GET', url: api2, dataType: 'json', success: function (data2) { console.log(`${data1.name} and ${data2.name} are two characters in Game of Thrones`) } }) } }) } fetchData()
// Promise var api1 = 'https://anapioficeandfire.com/api/characters/583' var api2 = 'https://anapioficeandfire.com/api/characters/584' function fetchData () { fetch(api1).then(res1 => { res1.json().then(data1 => { fetch(api2).then(res2 => { res2.json().then(data2 => console.log(`${data1.name} and ${data2.name} are two characters in Game of Thrones`)) }) }) }) } fetchData()
// generator var api1 = 'https://anapioficeandfire.com/api/characters/583' var api2 = 'https://anapioficeandfire.com/api/characters/584' function *fetchData () { var name1 = yield request(api1) var name2 = yield request(api2) console.log(`${name1} and ${name2} are two characters in Game of Thrones`) } function request (url) { fetch(url).then(res => res.json()).then(data => it.next(data.name)) } var it = fetchData() it.next()
// async/await var api1 = 'https://anapioficeandfire.com/api/characters/583' var api2 = 'https://anapioficeandfire.com/api/characters/584' async function fetchData () { var name1 = await request(api1) var name2 = await request(api2) console.log(`${name1} and ${name2} are two characters in Game of Thrones`) } function request (url) { return fetch(url).then(res => res.json()).then(data => data.name) } fetchData()
Express 使用 callback 捕獲異常,對於深層次的異常捕獲不了,Koa 使用 try catch,能更好地解決異常捕獲。
// Express callback app.use(function (err, req, res, next) { console.error(err.stack) res.status(500).send('Something broke!') })
// Koa generator app.use(function *(next) { try { yield next } catch (err) { this.status = err.status || 500 this.body = { message: err.message } this.app.emit('error', err, this) } })
// Koa async/await app.use(async (ctx, next) => { try { await next() } catch (err) { ctx.status = err.status || 500 ctx.body = { message: err.message } ctx.app.emit('error', err, this) } })
優勢:線性邏輯,經過中間件形式把業務邏輯細分、簡化,一個請求進來通過一系列中間件處理後再響應給用戶,清晰明瞭。 缺點:基於 callback 組合業務邏輯,業務邏輯複雜時嵌套過多,異常捕獲困難。
優勢:首先,藉助 co 和 generator,很好地解決了異步流程控制和異常捕獲問題。其次,Koa 把 Express 中內置的 router、view 等功能都移除了,使得框架自己更輕量。 缺點:社區相對較小