Как среда веб-разработки следующего поколения, Koa не только позволяет нам испытать трудности написания асинхронного кода синхронным способом, обеспечиваемым синтаксисом async/await, но и его краткие функции более благоприятны для разработчиков, чтобы расширяться в сочетании с бизнесом. сам.
Эта статья интерпретирует исходный код Koa со следующих аспектов:
- Инкапсулируйте функцию создания приложения
- Расширить разрешение и требование
- Принцип реализации промежуточного программного обеспечения
- Обработка исключений
1. Инкапсулируйте функцию для создания приложения
С помощью NodeJS легко написать простое приложение:
const http = require('http')
const server = http.createServer((req, res) => {
// 每一次请求处理的方法
console.log(req.url)
res.writeHead(200, { 'Content-Type': 'text/plain' })
res.end('Hello NodeJS')
})
server.listen(8080)
Примечание. Requests/favicon.ico прикрепляется, когда браузер отправляет запрос.
И Koa в основном выполняет следующие процессы в методе инкапсуляции и создания приложений:
- Промежуточное ПО организации (перед прослушиванием запросов)
- Создать объект контекста контекста
- Выполнение промежуточного программного обеспечения
- Выполнить метод ответа по умолчанию или метод обработки исключений
// application.js
listen(...args) {
const server = http.createServer(this.callback());
return server.listen(...args);
}
callback() {
// 组织中间件
const fn = compose(this.middleware);
// 未监听异常处理,则采用默认的异常处理方法
if (!this.listenerCount('error')) this.on('error', this.onerror);
const handleRequest = (req, res) => {
// 生成context上下文对象
const ctx = this.createContext(req, res);
return this.handleRequest(ctx, fn);
};
return handleRequest;
}
handleRequest(ctx, fnMiddleware) {
const res = ctx.res;
// 默认状态码为404
res.statusCode = 404;
// 中间件执行完毕之后 采用默认的 错误 与 成功 的处理方式
const onerror = err => ctx.onerror(err);
const handleResponse = () => respond(ctx);
onFinished(res, onerror);
return fnMiddleware(ctx).then(handleResponse).catch(onerror);
}
2. Расширить разрешение и требование
Прежде всего, нам нужно знать, что req и req в NodeJS являются экземплярами http.IncomingMessage и http.ServerResponse, затем мы можем расширить req и res в NodeJS следующим образом:
Object.defineProperties(http.IncomingMessage.prototype, {
query: {
get () {
return querystring.parse(url.parse(this.url).query)
}
}
})
Object.defineProperties(http.ServerResponse.prototype, {
json: {
value: function (obj) {
if (typeof obj === 'object') {
obj = JSON.stringify(obj)
}
this.end(obj)
}
}
})
В Koa объекты запроса и ответа настраиваются, а затем сохраняется ссылка на res и req, и, наконец, расширение реализуется с помощью методов получения и установки.
// application.js
createContext(req, res) {
const context = Object.create(this.context);
const request = context.request = Object.create(this.request);
const response = context.response = Object.create(this.response);
context.app = request.app = response.app = this;
context.req = request.req = response.req = req; // 保存原生req对象
context.res = request.res = response.res = res; // 保存原生res对象
request.ctx = response.ctx = context;
request.response = response;
response.request = request;
context.originalUrl = request.originalUrl = req.url;
context.state = {};
// 最终返回完整的context上下文对象
return context;
}
Итак, в Коа различают эти две группы объектов:
- запрос, ответ: объекты, расширенные Koa
- res, req: собственные объекты NodeJS.
// request.js
get header() {
return this.req.headers;
},
set header(val) {
this.req.headers = val;
},
На данный момент вы уже можете получить доступ к свойству заголовка следующим образом:
ctx.request.header
Однако, чтобы облегчить разработчикам вызов этих свойств и методов, KOA будет проксировать свойства и методы в ответе и запросе к контексту.
Через Object.defineProperty вы можете легко реализовать прокси свойства:
function access (proto, target, name) {
Object.defineProperty(proto, name, {
get () {
return target[name]
},
set (value) {
target[name] = value
}
})
}
access(context, request, 'header')
Для прокси метода нужно обратить внимание на указание этого:
function method (proto, target, name) {
proto[name] = function () {
return target[name].apply(target, arguments)
}
}
Выше приведен основной код прокси атрибута и прокси метода, который в основном представляет собой обычную процедуру.
Подробный исходный код этой части агента можно посмотретьnode-delegates, но этот пакет существует уже давно, и некоторые старые методы были упразднены.
Исходный код описанного выше процесса включает в себя много базовых знаний JavaScript, таких как: прототипное наследование, это указание. Ученикам со слабым фундаментом также необходимо сначала понять эти базовые знания.
В-третьих, принцип реализации промежуточного программного обеспечения
Прежде всего, необходимо прояснить: промежуточное ПО — это не концепция NodeJS, а просто концепция, полученная из фреймворков connect, express и koa.
1. Дизайн промежуточного программного обеспечения для подключения
В Connect разработчики могут зарегистрировать промежуточное ПО с помощью метода use:
function use(route, fn) {
var handle = fn;
var path = route;
// 不传入route则默认为'/',这种基本是框架处理参数的一种套路
if (typeof route !== 'string') {
handle = route;
path = '/';
}
...
// 存储中间件
this.stack.push({ route: path, handle: handle });
// 以便链式调用
return this;
}
Метод use внутренне получает информацию о маршрутизации промежуточного ПО (значение по умолчанию — '/') и функции обработки промежуточного ПО, встраивает их в объект слоя и сохраняет в очереди, которая является стеком в приведенном выше коде.
Поток выполнения промежуточного программного обеспечения connect в основном определяется функциями дескриптора и вызова:
function handle(req, res, out) {
var index = 0;
var stack = this.stack;
...
function next(err) {
...
// 依次取出中间件
var layer = stack[index++]
// 终止条件
if (!layer) {
defer(done, err);
return;
}
var path = parseUrl(req).pathname || '/';
var route = layer.route;
// 路由匹配规则
if (path.toLowerCase().substr(0, route.length) !== route.toLowerCase()) {
return next(err);
}
...
call(layer.handle, route, err, req, res, next);
}
next();
}
Функция handle использует функцию замыкания next, чтобы определить, соответствует ли уровень текущему маршруту.Если он соответствует, выполните функцию промежуточного программного обеспечения на уровне, в противном случае продолжите проверку следующего уровня.
Здесь следует отметить, что способ проверки маршрута в следующем может не совпадать с предполагаемым, поэтому промежуточное ПО, чей маршрут по умолчанию — «/», будет выполняться при каждой обработке запроса.
function call(handle, route, err, req, res, next) {
var arity = handle.length;
var error = err;
var hasError = Boolean(err);
try {
if (hasError && arity === 4) {
// 错误处理中间件
handle(err, req, res, next);
return;
} else if (!hasError && arity < 4) {
// 请求处理中间件
handle(req, res, next);
return;
}
} catch (e) {
// 记录错误
error = e;
}
// 将错误传递下去
next(error);
}
При выполнении метода промежуточного программного обеспечения через метод вызова для перехвата ошибки используется try/catch.Здесь следует обратить внимание на особый момент.Вызов будет решать, следует ли выполнять промежуточное программное обеспечение обработки ошибок в зависимости от того, есть ли ошибка и параметры функции промежуточного программного обеспечения. И как только ошибка будет обнаружена, следующий метод передаст ошибку дальше, поэтому, даже если обычное промежуточное ПО обработки запросов передаст сопоставление маршрута в следующем, оно все равно будет отфильтровано методом вызова.
Схема обработки слоя:
Вышеизложенное является основными моментами проектирования промежуточного программного обеспечения подключения, которые можно резюмировать следующим образом:
- Зарегистрируйте промежуточное ПО через метод use;
- Порядок промежуточного программного обеспечения является подключение через следующий метод и необходимо вызвать вручную, и выполняется соответствие маршрутизации в следующем, что фильтрует частичное промежуточное программное обеспечение;
- Когда во время выполнения промежуточного программного обеспечения возникает исключение, next будет нести исключение для фильтрации промежуточного программного обеспечения без ошибок, поэтому промежуточное программное обеспечение ошибки имеет на один параметр ошибки больше, чем другое промежуточное программное обеспечение;
- В цикле обработки запроса вам нужно вручную вызвать res.end(), чтобы завершить ответ;
2. Дизайн промежуточного ПО Koa
Дизайн промежуточного программного обеспечения Koa и промежуточного программного обеспечения подключения сильно различается:
- Выполнение промежуточного программного обеспечения Koa не обязательно должно соответствовать маршруту, поэтому зарегистрированное промежуточное программное обеспечение будет выполняться для каждого запроса. (Конечно, вам все равно нужно вручную вызывать next);
- В Koa при наследовании события событие ошибки становится доступным, что позволяет разработчикам настраивать обработку исключений;
- В Koa res.end автоматически вызывается после выполнения промежуточного программного обеспечения, чтобы не забыть вызвать res.end при подключении, в результате чего пользователь не получит никакой обратной связи.
- KOA использует синтаксис Async / a ждать, чтобы разработчики писать асинхронный код в синхронном виде.
Конечно, Koa также использует метод использования для регистрации промежуточного программного обеспечения.По сравнению с подключением обработка сопоставления маршрутов опущена, что очень лаконично:
use(fn) {
this.middleware.push(fn);
return this;
}
и использование поддерживает связанные вызовы.
Процесс выполнения промежуточного программного обеспечения Koa в основном завершается функцией компоновки в koa-compose:
function compose (middleware) {
if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!')
for (const fn of middleware) {
if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!')
}
/**
* @param {Object} context
* @return {Promise}
* @api public
*/
return function (context, next) {
let index = -1
return dispatch(0)
function dispatch (i) {
if (i <= index) return Promise.reject(new Error('next() called multiple times'))
index = i
let fn = middleware[i]
if (i === middleware.length) fn = next
if (!fn) return Promise.resolve()
try {
// 递归调用下一个中间件
return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
} catch (err) {
return Promise.reject(err)
}
}
}
}
Видя, что идея подключения и реализации koa промежуточного программного обеспечения носит рекурсивный характер, нетрудно увидеть, что koa более лаконичен, чем connect.Основные причины таковы:
- Функция сопоставления маршрутов предусмотрена в connect, а в Koa она эквивалентна пути '/' по умолчанию в connect.
- Когда соединение фиксирует исключение промежуточного программного обеспечения, оно передает ошибку дальше, чтобы проверить промежуточное программное обеспечение одно за другим, пока промежуточное программное обеспечение не будет обработано ошибкой, в то время как в Koa промежуточное программное обеспечение обернуто обещанием, Как только в промежуточном программном обеспечении возникает исключение, он напрямую вызовет состояние отклонения, просто разберитесь с ним в улове Promise.
Вышеизложенный принцип реализации промежуточного программного обеспечения подключения и промежуточного программного обеспечения Koa Теперь, глядя на эту блок-схему выполнения промежуточного программного обеспечения Koa, не должно быть никаких сомнений, верно? !
В-четвертых, обработка исключений
Для синхронного кода исключения можно легко перехватывать с помощью try/catch, а перехват исключений в промежуточном программном обеспечении подключения выполняется с помощью try/catch.
Для асинхронного кода try/catch не может быть пойман.В это время цепочка промисов обычно может быть построена, а ошибки перехватываются в финальном методе catch.Это то, как Koa обрабатывает это, и событие ошибки отправляется в методе catch чтобы разработчики могли настраивать логику обработки исключений.
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 = err
}
})
V. Резюме
Я считаю, что когда вы увидите здесь и вспомните проблемы, с которыми сталкивались раньше, у вас должно появиться новое понимание, и вам будет удобнее снова использовать Koa, что также является одной из целей анализа исходного кода Koa.