「这是我参与11月更文挑战的第18天,活动详情查看:2021最后一次更文挑战」
本文为 koa 依赖系列文章,前几篇也可以在站内查看:
- koa 中依赖的库 parseurl
- Koa 依赖的库 type-is 和 content-disposition
- Koa 依赖的库 accepts、content-type 和 cache-content-type
- Koa 依赖的库 encodeurl 和 escape-html
- Koa 中依赖的库 statuses
- Koa 依赖的库 cookies
on-finished 可以监听 response 的结束事件,在 koa application 的 handleRequest 中,通过 onFinished 来监听 res 提前结束的异常场景,在 response 的 body 中,如果传输的是 stream,通过 onFinished 来监听 res 结束来做清理工作。
on-finished 默认导出 onFinished 事件监听,同时也导出了判断方法 isFinished。
先看 isFinished 的实现:
function isFinished (msg) {
var socket = msg.socket
if (typeof msg.finished === 'boolean') {
// OutgoingMessage
return Boolean(msg.finished || (socket && !socket.writable))
}
if (typeof msg.complete === 'boolean') {
// IncomingMessage
return Boolean(msg.upgrade || !socket || !socket.readable || (msg.complete && !msg.readable))
}
// don't know
return undefined
}
isFinished 接收 msg 作为参数,这个 msg 可以传 IncomingMessage 或 OutgoingMessage,这里通过 finished 属性来判断消息类型,之后根据 socket 状态来判断是否为 finish 状态。
再看 onFinished 的实现,如果添加监听时已经是 isFinished 状态,这里会立即执行一次异步回调,这里的异步是使用 setImmediate 或 process.nextTick 实现的:
var defer = typeof setImmediate === 'function'
? setImmediate
: function (fn) { process.nextTick(fn.bind.apply(fn, arguments)) }
之后添加监听事件,这里会把事件监听器挂在 msg 对象的 __onFinished 属性上,多个事件使用 queue 保存。实际的 finish 事件绑定位于 attachFinishedListener 函数中:
function attachFinishedListener (msg, callback) {
var eeMsg
var eeSocket
var finished = false
function onFinish (error) {
eeMsg.cancel()
eeSocket.cancel()
finished = true
callback(error)
}
// finished on first message event
eeMsg = eeSocket = first([[msg, 'end', 'finish']], onFinish)
function onSocket (socket) {
// remove listener
msg.removeListener('socket', onSocket)
if (finished) return
if (eeMsg !== eeSocket) return
// finished on first socket event
eeSocket = first([[socket, 'error', 'close']], onFinish)
}
if (msg.socket) {
// socket already assigned
onSocket(msg.socket)
return
}
// wait for socket to be assigned
msg.on('socket', onSocket)
if (msg.socket === undefined) {
// istanbul ignore next: node.js 0.8 patch
patchAssignSocket(msg, onSocket)
}
}
这里首先监听 msg 的 end 和 finish 事件,如果有一个触发就直接执行回调,之后监听 socket 上的事件,如果收到 error 或 close 也会触发回调。对于旧版本 node,socket 为 assignSocket,因此这里做了一个 patchAssignSocket 的逻辑。
on-finished 库主要是做一些内部事件的监听,阅读源码可以了解事件的实际发生源。而前面提到,koa 在收到 on-finished 结束事件是会进行流的 destroy 处理,这里又实用了另一个库 destroy。
destroy 的用途是销毁一个 stream,在 koa 中用在 response body 的 setter 中:
if (val instanceof Stream) {
onFinish(this.res, destroy.bind(null, val))
// ...
}
如果是 Stream 类型数据,在收到 onFinish 时直接把数据传入 destroy 来清理。destroy 默认导出一个函数,接收 stream 参数:
function destroy (stream) {
if (stream instanceof ReadStream) {
return destroyReadStream(stream)
}
if (!(stream instanceof Stream)) {
return stream
}
if (typeof stream.destroy === 'function') {
stream.destroy()
}
return stream
}
内部最终调用的还是 stream 的 destroy 方法,只是对于 ReadStream 进行了一些特殊处理:如果是 ReadStream 类型,调用 destroy 后可能还会收到 open 事件,因此这里添加了一个 onOpenClose 方法来确保流可以被彻底销毁:
function destroyReadStream (stream) {
stream.destroy()
if (typeof stream.close === 'function') {
// node.js core bug work-around
stream.on('open', onOpenClose)
}
return stream
}
function onOpenClose () {
if (typeof this.fd === 'number') {
// actually close down the fd
this.close()
}
}
以上就是这两个库的全部源码了,这一部分其实比较简单,这两个库主要是对一些边界 case 进行统一化的处理和封装,逻辑比较简单。