diff --git a/HISTORY.md b/HISTORY.md index 5e8549a..4ed26bc 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -2,6 +2,7 @@ unreleased ========== * remove support for non-`res` argument + * support both `req` and `res` as arguments * deps: ee-first@1.0.5 1.2.2 / 2014-06-10 diff --git a/README.md b/README.md index cb10101..17c659e 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,14 @@ $ npm install finished ## API -### finished(response, callback) +### finished(res, listener) + +Attach a listener to listen for the response to finish. The listener will +be invoked only once when the response finished. If the response finished +to to an error, the first argument will contain the error. + +Listening to the end of a response would be used to close things associated +with the response, like open files. ```js var onFinished = require('finished') @@ -25,6 +32,29 @@ onFinished(res, function (err) { }) ``` +### finished(req, listener) + +Attach a listener to listen for the request to finish. The listener will +be invoked only once when the request finished. If the request finished +to to an error, the first argument will contain the error. + +Listening to the end of a request would be used to know when to continue +after reading the data. + +```js +var data = '' +var onFinished = require('finished') + +req.setEncoding('utf8') +res.on('data', function (str) { + data += str +}) + +onFinished(req, function (err) { + // if err, data is probably incomplete +}) +``` + ### Example The following code ensures that file descriptors are always closed diff --git a/index.js b/index.js index 6a6a6c7..d765482 100644 --- a/index.js +++ b/index.js @@ -29,20 +29,20 @@ var defer = typeof setImmediate === 'function' * @api public */ -module.exports = function finished(res, callback) { - var socket = res.socket +module.exports = function finished(msg, callback) { + var socket = msg.socket - if (res.finished || !socket.writable) { + if (msg.finished || !socket.writable) { defer(callback) - return res + return msg } - var listener = res.__onFinished + var listener = msg.__onFinished // create a private single listener with queue if (!listener || !listener.queue) { - listener = res.__onFinished = function onFinished(err) { - if (res.__onFinished === listener) res.__onFinished = null + listener = msg.__onFinished = function onFinished(err) { + if (msg.__onFinished === listener) msg.__onFinished = null var queue = listener.queue || [] while (queue.length) queue.shift()(err) } @@ -51,11 +51,11 @@ module.exports = function finished(res, callback) { // finished on first event first([ [socket, 'error', 'close'], - [res, 'finish'], + [msg, 'end', 'finish'], ], listener) } listener.queue.push(callback) - return res + return msg } diff --git a/test/test.js b/test/test.js index 5321811..12fd1b6 100644 --- a/test/test.js +++ b/test/test.js @@ -4,7 +4,7 @@ var http = require('http') var net = require('net') var onFinished = require('..') -describe('finished', function () { +describe('finished(res, listener)', function () { describe('when the response finishes', function () { it('should fire the callback', function (done) { var server = http.createServer(function (req, res) { @@ -127,6 +127,143 @@ describe('finished', function () { }) }) +describe('finished(req, listener)', function () { + describe('when the request finishes', function () { + it('should fire the callback', function (done) { + var server = http.createServer(function (req, res) { + onFinished(req, done) + req.resume() + setTimeout(res.end.bind(res), 0) + }) + server.listen(function () { + var port = this.address().port + http.get('http://127.0.0.1:' + port, function (res) { + res.resume() + res.on('close', server.close.bind(server)) + }) + }) + }) + + it('should fire when called after finish', function (done) { + var server = http.createServer(function (req, res) { + onFinished(req, function () { + onFinished(req, done) + }) + req.resume() + setTimeout(res.end.bind(res), 0) + }) + server.listen(function () { + var port = this.address().port + http.get('http://127.0.0.1:' + port, function (res) { + res.resume() + res.on('close', server.close.bind(server)) + }) + }) + }) + }) + + describe('when using keep-alive', function () { + it('should fire for each request', function (done) { + var called = false + var server = http.createServer(function (req, res) { + var data = '' + + onFinished(req, function (err) { + assert.ifError(err) + assert.equal(data, 'A') + + if (called) { + socket.end() + server.close() + done(called !== req ? null : new Error('fired twice on same req')) + return + } + + called = req + + res.end() + writerequest(socket, true) + }) + + req.setEncoding('utf8') + req.on('data', function (str) { + data += str + }) + + socket.write('1\r\nA\r\n') + socket.write('0\r\n\r\n') + }) + var socket + + server.listen(function () { + socket = net.connect(this.address().port, function () { + writerequest(this, true) + }) + }) + }) + }) + + describe('when request errors', function () { + it('should fire with error', function (done) { + var server = http.createServer(function (req, res) { + onFinished(req, function (err) { + assert.ok(err) + done() + }) + + socket.on('error', noop) + socket.write('W') + }) + var socket + + server.listen(function () { + socket = net.connect(this.address().port, function () { + writerequest(this, true) + }) + }) + }) + }) + + describe('when the request aborts', function () { + it('should execute the callback', function (done) { + var client + var server = http.createServer(function (req, res) { + onFinished(req, done) + setTimeout(client.abort.bind(client), 0) + }) + server.listen(function () { + var port = this.address().port + client = http.get('http://127.0.0.1:' + port) + client.on('error', noop) + }) + }) + }) + + describe('when calling many times on same request', function () { + it('should not print warnings', function (done) { + var server = http.createServer(function (req, res) { + var stderr = captureStderr(function () { + for (var i = 0; i < 400; i++) { + onFinished(req, noop) + } + }) + + onFinished(req, done) + assert.equal(stderr, '') + res.end() + }) + + server.listen(function () { + var port = this.address().port + http.get('http://127.0.0.1:' + port, function (res) { + res.resume() + res.on('close', server.close.bind(server)) + }) + }) + }) + }) +}) + function captureStderr(fn) { var chunks = [] var write = process.stderr.write @@ -156,5 +293,4 @@ function writerequest(socket, chunked) { } socket.write('\r\n') - socket.write('\r\n') }