Koa v2 недавно упал, поскольку Node сделал async-await общедоступным без флага. Кажется, что Express по-прежнему побеждает в конкурсе на популярность, но я с радостью использую Koa с момента анонса версии 2 и всегда боюсь возвращаться в Express для старых проектов.
Я время от времени тусуюсь на канале Koa Gitter, чтобы ответить на вопросы, и те, которые я получаю больше всего, связаны с волшебной системой промежуточного программного обеспечения Koa, поэтому я подумал, что сделаю рецензию по этому поводу.
Многие новички Koa работали с Express раньше, поэтому я буду проводить много сравнений между ними.
Эта статья предназначена для новичков в Koa и людей, которые рассматривают Koa для своего следующего проекта.
Основы
Начнем с самого необходимого. И в Koa, и в Express все, что связано с HTTP-запросом, будет выполняться внутри промежуточного программного обеспечения. Самая важная концепция для понимания - передача продолжения промежуточного программного обеспечения. Это звучит ужасно причудливо, но на самом деле это не так. Идея состоит в том, что после того, как промежуточное ПО завершит свою работу, оно может при желании вызвать следующее промежуточное ПО в цепочке.
выражать
const express = require('express') const app = express() // Middleware 1 app.use((req, res, next) => { res.status(200) console.log('Setting status') // Call the next middleware next() }) // Middleware 2 app.use((req, res) => { console.log('Setting body') res.send(`Hello from Express`) }) app.listen(3001, () => console.log('Express app listening on 3001'))
Коа
const Koa = require('koa') const app = new Koa() // Middleware 1 app.use(async (ctx, next) => { ctx.status = 200 console.log('Setting status') // Call the next middleware, wait for it to complete await next() }) // Middleware 2 app.use((ctx) => { console.log('Setting body') ctx.body = 'Hello from Koa' }) app.listen(3002, () => console.log('Koa app listening on 3002'))
Давайте поразим их обоих curl
:
$ curl http://localhost:3001 Hello from Express $ curl http://localhost:3002 Hello from Koa
Оба примера делают одно и то же, и оба выводят на терминал один и тот же вывод:
Setting status Setting body
Это показывает, что в обоих случаях промежуточное ПО выполняется сверху вниз.
Большая разница здесь в том, что цепочка промежуточного программного обеспечения Express основана на обратных вызовах, в то время как Koa - на основе Promise.
Посмотрим, что произойдет, если мы опустим вызов next()
в обоих примерах.
выражать
$ curl http://localhost:3001
… Он никогда не завершается. Это связано с тем, что в Express вам нужно либо позвонить next()
, либо отправить ответ, иначе запрос не будет выполнен.
Коа
$ curl http://localhost:3002 OK
Ах, значит, приложение Koa завершит запрос, но без тела. Тем не менее, он установил код состояния. Так что 2-е промежуточное ПО не было вызвано.
Но есть еще одна важная вещь для Коа. Если вы позвоните next()
, вы должны дождаться этого!
Лучше всего это проиллюстрировано на следующем примере:
// Simple Promise delay function delay (ms) { return new Promise((resolve) => { setTimeout(resolve, ms) }) } app.use(async (ctx, next) => { ctx.status = 200 console.log('Setting status') next() // forgot await! }) app.use(async (ctx) => { await delay(1000) // simulate actual async behavior console.log('Setting body') ctx.body = 'Hello from Koa' })
Давай посмотрим что происходит.
$ curl http://localhost:3002 OK
Хм, мы позвонили next()
, но тела не прислали? Это потому, что Koa завершает запрос после разрешения цепочки промисов промежуточного программного обеспечения. Это означает, что ответ был отправлен клиенту до того, как мы успели установить ctx.body
!
Еще одна проблема, если вы используете простой Promise.then()
вместо async-await
, заключается в том, что промежуточное ПО должно возвращать обещание. Когда возвращенное обещание разрешится, Koa возобновит работу с предыдущим промежуточным программным обеспечением.
app.use((ctx, next) => { ctx.status = 200 console.log('Setting status') // need to return here, not using async-await return next() })
Лучший пример использования простых обещаний:
// We don't call `next()` because // we don't want anything else to happen. app.use((ctx) => { return delay(1000).then(() => { console.log('Setting body') ctx.body = 'Hello from Koa' }) })
Промежуточное ПО Koa - особенность, меняющая правила игры
В предыдущем разделе я писал:
Тогда Koa возобновит работу с предыдущим промежуточным ПО.
И это могло немного сбить вас с толку. Позвольте мне объяснить.
В Express промежуточное ПО может делать полезные вещи только до вызова next()
, но не после. Как только вы позвоните next()
, этот запрос никогда больше не коснется промежуточного программного обеспечения. Это может быть своего рода облом. Люди (включая самих авторов Express) нашли хитрые обходные пути, например, наблюдая за потоком ответов, когда пишутся заголовки, но для среднего потребителя это просто неудобно.
Например, для реализации промежуточного программного обеспечения, которое записывает количество времени, необходимое для выполнения запроса и отправки его в заголовке X-ResponseTime
, потребуется кодовая точка «до следующего вызова» и кодовая точка «после вызова следующего». В Express это реализовано с помощью техники просмотра потокового видео.
Попробуем реализовать это на Koa.
async function responseTime (ctx, next) { console.log('Started tracking response time') const started = Date.now() await next() // once all middleware below completes, this continues const ellapsed = (Date.now() - started) + 'ms' console.log('Response time is:', ellapsed) ctx.set('X-ResponseTime', ellapsed) } app.use(responseTime) app.use(async (ctx, next) => { ctx.status = 200 console.log('Setting status') await next() }) app.use(async (ctx) => { await delay(1000) console.log('Setting body') ctx.body = 'Hello from Koa' })
8 строк. Это все, что нужно. Никакого фанкового обнюхивания потока, только великолепный код async-await. Давай ударим! Флаг -i
указывает curl
также показать нам заголовки ответа.
$ curl -i http://localhost:3002 HTTP/1.1 200 OK Content-Type: text/plain; charset=utf-8 Content-Length: 14 X-ResponseTime: 1001ms Date: Thu, 30 Mar 2017 12:52:48 GMT Connection: keep-alive Hello from Koa
Отлично! Мы получили время ответа в HTTP-заголовке. Давайте проверим наш терминал на наличие журналов, чтобы увидеть, в каком порядке были записаны журналы консоли.
Started tracking response time Setting status Setting body Response time is: 1001ms
Вот и все. Koa дает нам полный контроль над потоком промежуточного программного обеспечения. Внедрить такие вещи, как аутентификация и обработка ошибок, будет очень просто!
Обработка ошибок
Это моя самая любимая вещь в Koa, и она обеспечивается цепочкой промисов промежуточного программного обеспечения, о которой я подробно рассказывал выше.
Для наглядности давайте посмотрим, как мы это сделаем в Express.
выражать
Обработка ошибок выполняется в промежуточном программном обеспечении со специальной подписью, и для работы его необходимо добавить в конец цепочки.
app.use((req, res) => { if (req.query.greet !== 'world') { throw new Error('can only greet "world"') } res.status(200) res.send(`Hello ${req.query.greet} from Express`) }) // Error handler app.use((err, req, res, next) => { if (!err) { next() return } console.log('Error handler:', err.message) res.status(400) res.send('Uh-oh: ' + err.message) })
Это лучший пример сценария. Если вы имеете дело с асинхронными ошибками из обратных вызовов или обещаний, они становятся чрезвычайно подробными. Например:
app.use((req, res, next) => { loadCurrentWeather(req.query.city, (err, weather) => { if (err) { return next(err) } loadForecast(req.query.city, (err, forecast) => { if (err) { return next(err) } res.status(200).send({ weather: weather, forecast: forecast }) }) }) next() })
Я полностью осведомлен о модулях, которые упрощают работу с обратным вызовом, это просто чтобы продемонстрировать, что простая обработка ошибок в Express становится громоздкой. Не говоря уже о том, что вам необходимо учитывать как ошибки асинхронизации, так и ошибки синхронизации.
Коа
Обработка ошибок также выполняется с помощью обещаний. Koa всегда будет заключать next()
в обещание для нас, поэтому нам даже не нужно беспокоиться об ошибках асинхронности и синхронизации.
Промежуточное ПО для обработки ошибок находится наверху, потому что оно «оборачивает» каждое последующее промежуточное ПО. Это означает, что любая ошибка, возникшая в промежуточном программном обеспечении, добавленном после обработки ошибок, будет обнаружена (да, почувствуйте силу!)
app.use(async (ctx, next) => { try { await next() } catch (err) { ctx.status = 400 ctx.body = `Uh-oh: ${err.message}` console.log('Error handler:', err.message) } }) app.use(async (ctx) => { if (ctx.query.greet !== 'world') { throw new Error('can only greet "world"') } console.log('Sending response') ctx.status = 200 ctx.body = `Hello ${ctx.query.greet} from Koa` })
да. А try-catch
. Для обработки ошибок. Как кстати! Не async-await
способ:
app.use((ctx, next) => { return next().catch(err => { ctx.status = 400 ctx.body = `Uh-oh: ${err.message}` console.log('Error handler:', err.message) }) })
Попробуем вызвать ошибку.
$ curl http://localhost:3002?greet=jeff Uh-oh: can only greet "world"
И вывод консоли, как ожидалось:
Error handler: can only greet "world"
Маршрутизация
В отличие от Express, у Koa нет практически ничего из коробки. Ни бодипарсера, ни роутера тоже.
В Koa есть несколько вариантов маршрутизации, например koa-route
и koa-router
. Я предпочитаю последнее.
выражать
Маршрутизация в Express встроена.
app.get('/todos', (req, res) => { res.status(200).send([{ id: 1, text: 'Switch to Koa' }, { id: 2, text: '???' }, { id: 3, text: 'Profit' }]) })
Коа
В этом примере я выбрал koa-router
, потому что это то, что я использую.
const Router = require('koa-router') const router = new Router() router.get('/todos', (ctx) => { ctx.status = 200 ctx.body = [{ id: 1, text: 'Switch to Koa', completed: true }, { id: 2, text: '???', completed: true }, { id: 3, text: 'Profit', completed: true }] }) app.use(router.routes()) // makes sure a 405 Method Not Allowed is sent app.use(router.allowedMethods())
Заключение
Коа классный. Полный контроль над цепочкой промежуточного программного обеспечения и тот факт, что все это основано на обещаниях, значительно упрощает работу со всем. Нет больше if (err) return next(err)
повсюду, только обещания.
С помощью сверхнадежного обработчика ошибок мы можем выдавать ошибки, чтобы более элегантно оторваться от счастливого пути нашего кода (подумайте об ошибках проверки, нарушениях бизнес-логики).
Вот список промежуточного программного обеспечения, которое я часто использую (в произвольном порядке):
На заметку: не все промежуточное ПО поддерживает Koa 2, однако их можно преобразовать во время выполнения с помощью koa-convert
, так что не беспокойтесь.
Надеюсь, эта статья оказалась для вас полезной. Если да, нажмите кнопку «Рекомендовать». Это было бы прекрасно! :)
Вы можете найти меня в Twitter: @Jeffijoe