| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent a890771 commit 4a2bd69
10 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -281,6 +281,9 @@ The stream is not closed when the `'error'` event is emitted unless the | |||
| 281 | 281 | [`autoDestroy`][writable-new] option was set to `true` when creating the | |
| 282 | 282 | stream. | |
| 283 | 283 | ||
| 284 | + After `'error'`, no further events other than `'close'` *should* be emitted | ||
| 285 | + (including `'error'` events). | ||
| 286 | + | ||
| 284 | 287 | ##### Event: 'finish' | |
| 285 | 288 | <!-- YAML | |
| 286 | 289 | added: v0.9.4 | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -117,6 +117,9 @@ function ReadableState(options, stream, isDuplex) { | |||
| 117 | 117 | this.resumeScheduled = false; | |
| 118 | 118 | this.paused = true; | |
| 119 | 119 | ||
| 120 | + // True if the error was already emitted and should not be thrown again | ||
| 121 | + this.errorEmitted = false; | ||
| 122 | + | ||
| 120 | 123 | // Should close be emitted on destroy. Defaults to true. | |
| 121 | 124 | this.emitClose = options.emitClose !== false; | |
| 122 | 125 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -429,13 +429,11 @@ function onwriteError(stream, state, sync, er, cb) { | |||
| 429 | 429 | // This can emit finish, and it will always happen | |
| 430 | 430 | // after error | |
| 431 | 431 | process.nextTick(finishMaybe, stream, state); | |
| 432 | - stream._writableState.errorEmitted = true; | ||
| 433 | 432 | errorOrDestroy(stream, er); | |
| 434 | 433 | } else { | |
| 435 | 434 | // The caller expect this to happen before if | |
| 436 | 435 | // it is async | |
| 437 | 436 | cb(er); | |
| 438 | - stream._writableState.errorEmitted = true; | ||
| 439 | 437 | errorOrDestroy(stream, er); | |
| 440 | 438 | // This can emit finish, but finish must | |
| 441 | 439 | // always follow error | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,22 +1,37 @@ | |||
| 1 | 1 | 'use strict'; | |
| 2 | 2 | ||
| 3 | + function needError(stream, err) { | ||
| 4 | + if (!err) { | ||
| 5 | + return false; | ||
| 6 | + } | ||
| 7 | + | ||
| 8 | + const r = stream._readableState; | ||
| 9 | + const w = stream._writableState; | ||
| 10 | + | ||
| 11 | + if ((w && w.errorEmitted) || (r && r.errorEmitted)) { | ||
| 12 | + return false; | ||
| 13 | + } | ||
| 14 | + | ||
| 15 | + if (w) { | ||
| 16 | + w.errorEmitted = true; | ||
| 17 | + } | ||
| 18 | + if (r) { | ||
| 19 | + r.errorEmitted = true; | ||
| 20 | + } | ||
| 21 | + | ||
| 22 | + return true; | ||
| 23 | + } | ||
| 24 | + | ||
| 3 | 25 | // Undocumented cb() API, needed for core, not for public API | |
| 4 | 26 | function destroy(err, cb) { | |
| 5 | - const readableDestroyed = this._readableState && | ||
| 6 | - this._readableState.destroyed; | ||
| 7 | - const writableDestroyed = this._writableState && | ||
| 8 | - this._writableState.destroyed; | ||
| 27 | + const r = this._readableState; | ||
| 28 | + const w = this._writableState; | ||
| 9 | 29 | ||
| 10 | - if (readableDestroyed || writableDestroyed) { | ||
| 30 | + if ((w && w.destroyed) || (r && r.destroyed)) { | ||
| 11 | 31 | if (cb) { | |
| 12 | 32 | cb(err); | |
| 13 | - } else if (err) { | ||
| 14 | - if (!this._writableState) { | ||
| 15 | - process.nextTick(emitErrorNT, this, err); | ||
| 16 | - } else if (!this._writableState.errorEmitted) { | ||
| 17 | - this._writableState.errorEmitted = true; | ||
| 18 | - process.nextTick(emitErrorNT, this, err); | ||
| 19 | - } | ||
| 33 | + } else if (needError(this, err)) { | ||
| 34 | + process.nextTick(emitErrorNT, this, err); | ||
| 20 | 35 | } | |
| 21 | 36 | ||
| 22 | 37 | return this; | |
@@ -25,28 +40,19 @@ function destroy(err, cb) { | |||
| 25 | 40 | // We set destroyed to true before firing error callbacks in order | |
| 26 | 41 | // to make it re-entrance safe in case destroy() is called within callbacks | |
| 27 | 42 | ||
| 28 | - if (this._readableState) { | ||
| 29 | - this._readableState.destroyed = true; | ||
| 43 | + if (w) { | ||
| 44 | + w.destroyed = true; | ||
| 30 | 45 | } | |
| 31 | - | ||
| 32 | - // If this is a duplex stream mark the writable part as destroyed as well | ||
| 33 | - if (this._writableState) { | ||
| 34 | - this._writableState.destroyed = true; | ||
| 46 | + if (r) { | ||
| 47 | + r.destroyed = true; | ||
| 35 | 48 | } | |
| 36 | 49 | ||
| 37 | 50 | this._destroy(err || null, (err) => { | |
| 38 | - if (!cb && err) { | ||
| 39 | - if (!this._writableState) { | ||
| 40 | - process.nextTick(emitErrorAndCloseNT, this, err); | ||
| 41 | - } else if (!this._writableState.errorEmitted) { | ||
| 42 | - this._writableState.errorEmitted = true; | ||
| 43 | - process.nextTick(emitErrorAndCloseNT, this, err); | ||
| 44 | - } else { | ||
| 45 | - process.nextTick(emitCloseNT, this); | ||
| 46 | - } | ||
| 47 | - } else if (cb) { | ||
| 51 | + if (cb) { | ||
| 48 | 52 | process.nextTick(emitCloseNT, this); | |
| 49 | 53 | cb(err); | |
| 54 | + } else if (needError(this, err)) { | ||
| 55 | + process.nextTick(emitErrorAndCloseNT, this, err); | ||
| 50 | 56 | } else { | |
| 51 | 57 | process.nextTick(emitCloseNT, this); | |
| 52 | 58 | } | |
@@ -61,29 +67,36 @@ function emitErrorAndCloseNT(self, err) { | |||
| 61 | 67 | } | |
| 62 | 68 | ||
| 63 | 69 | function emitCloseNT(self) { | |
| 64 | - if (self._writableState && !self._writableState.emitClose) | ||
| 70 | + const r = self._readableState; | ||
| 71 | + const w = self._writableState; | ||
| 72 | + | ||
| 73 | + if (w && !w.emitClose) | ||
| 65 | 74 | return; | |
| 66 | - if (self._readableState && !self._readableState.emitClose) | ||
| 75 | + if (r && !r.emitClose) | ||
| 67 | 76 | return; | |
| 68 | 77 | self.emit('close'); | |
| 69 | 78 | } | |
| 70 | 79 | ||
| 71 | 80 | function undestroy() { | |
| 72 | - if (this._readableState) { | ||
| 73 | - this._readableState.destroyed = false; | ||
| 74 | - this._readableState.reading = false; | ||
| 75 | - this._readableState.ended = false; | ||
| 76 | - this._readableState.endEmitted = false; | ||
| 81 | + const r = this._readableState; | ||
| 82 | + const w = this._writableState; | ||
| 83 | + | ||
| 84 | + if (r) { | ||
| 85 | + r.destroyed = false; | ||
| 86 | + r.reading = false; | ||
| 87 | + r.ended = false; | ||
| 88 | + r.endEmitted = false; | ||
| 89 | + r.errorEmitted = false; | ||
| 77 | 90 | } | |
| 78 | 91 | ||
| 79 | - if (this._writableState) { | ||
| 80 | - this._writableState.destroyed = false; | ||
| 81 | - this._writableState.ended = false; | ||
| 82 | - this._writableState.ending = false; | ||
| 83 | - this._writableState.finalCalled = false; | ||
| 84 | - this._writableState.prefinished = false; | ||
| 85 | - this._writableState.finished = false; | ||
| 86 | - this._writableState.errorEmitted = false; | ||
| 92 | + if (w) { | ||
| 93 | + w.destroyed = false; | ||
| 94 | + w.ended = false; | ||
| 95 | + w.ending = false; | ||
| 96 | + w.finalCalled = false; | ||
| 97 | + w.prefinished = false; | ||
| 98 | + w.finished = false; | ||
| 99 | + w.errorEmitted = false; | ||
| 87 | 100 | } | |
| 88 | 101 | } | |
| 89 | 102 | ||
@@ -98,12 +111,12 @@ function errorOrDestroy(stream, err) { | |||
| 98 | 111 | // the error to be emitted nextTick. In a future | |
| 99 | 112 | // semver major update we should change the default to this. | |
| 100 | 113 | ||
| 101 | - const rState = stream._readableState; | ||
| 102 | - const wState = stream._writableState; | ||
| 114 | + const r = stream._readableState; | ||
| 115 | + const w = stream._writableState; | ||
| 103 | 116 | ||
| 104 | - if ((rState && rState.autoDestroy) || (wState && wState.autoDestroy)) | ||
| 117 | + if ((r && r.autoDestroy) || (w && w.autoDestroy)) | ||
| 105 | 118 | stream.destroy(err); | |
| 106 | - else | ||
| 119 | + else if (needError(stream, err)) | ||
| 107 | 120 | stream.emit('error', err); | |
| 108 | 121 | } | |
| 109 | 122 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -69,12 +69,14 @@ tcp.listen(0, common.mustCall(function() { | |||
| 69 | 69 | [], | |
| 70 | 70 | {} | |
| 71 | 71 | ].forEach((value) => { | |
| 72 | - common.expectsError(() => socket.write(value), { | ||
| 72 | + // We need to check the callback since 'error' will only | ||
| 73 | + // be emitted once per instance. | ||
| 74 | + socket.write(value, common.expectsError({ | ||
| 73 | 75 | code: 'ERR_INVALID_ARG_TYPE', | |
| 74 | 76 | type: TypeError, | |
| 75 | 77 | message: 'The "chunk" argument must be one of type string or Buffer. ' + | |
| 76 | 78 | `Received type ${typeof value}` | |
| 77 | - }); | ||
| 79 | + })); | ||
| 78 | 80 | }); | |
| 79 | 81 | ||
| 80 | 82 | // Write a string that contains a multi-byte character sequence to test that | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,19 @@ | |||
| 1 | + 'use strict'; | ||
| 2 | + const common = require('../common'); | ||
| 3 | + const { Writable, Readable } = require('stream'); | ||
| 4 | + | ||
| 5 | + { | ||
| 6 | + const writable = new Writable(); | ||
| 7 | + writable.on('error', common.mustCall()); | ||
| 8 | + writable.end(); | ||
| 9 | + writable.write('h'); | ||
| 10 | + writable.write('h'); | ||
| 11 | + } | ||
| 12 | + | ||
| 13 | + { | ||
| 14 | + const readable = new Readable(); | ||
| 15 | + readable.on('error', common.mustCall()); | ||
| 16 | + readable.push(null); | ||
| 17 | + readable.push('h'); | ||
| 18 | + readable.push('h'); | ||
| 19 | + } | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -3,17 +3,32 @@ | |||
| 3 | 3 | const common = require('../common'); | |
| 4 | 4 | const stream = require('stream'); | |
| 5 | 5 | ||
| 6 | - const readable = new stream.Readable({ | ||
| 7 | - read: () => {} | ||
| 8 | - }); | ||
| 9 | - | ||
| 10 | - function checkError(fn) { | ||
| 11 | - common.expectsError(fn, { | ||
| 6 | + function testPushArg(val) { | ||
| 7 | + const readable = new stream.Readable({ | ||
| 8 | + read: () => {} | ||
| 9 | + }); | ||
| 10 | + readable.on('error', common.expectsError({ | ||
| 12 | 11 | code: 'ERR_INVALID_ARG_TYPE', | |
| 13 | 12 | type: TypeError | |
| 13 | + })); | ||
| 14 | + readable.push(val); | ||
| 15 | + } | ||
| 16 | + | ||
| 17 | + testPushArg([]); | ||
| 18 | + testPushArg({}); | ||
| 19 | + testPushArg(0); | ||
| 20 | + | ||
| 21 | + function testUnshiftArg(val) { | ||
| 22 | + const readable = new stream.Readable({ | ||
| 23 | + read: () => {} | ||
| 14 | 24 | }); | |
| 25 | + readable.on('error', common.expectsError({ | ||
| 26 | + code: 'ERR_INVALID_ARG_TYPE', | ||
| 27 | + type: TypeError | ||
| 28 | + })); | ||
| 29 | + readable.unshift(val); | ||
| 15 | 30 | } | |
| 16 | 31 | ||
| 17 | - checkError(() => readable.push([])); | ||
| 18 | - checkError(() => readable.push({})); | ||
| 19 | - checkError(() => readable.push(0)); | ||
| 32 | + testUnshiftArg([]); | ||
| 33 | + testUnshiftArg({}); | ||
| 34 | + testUnshiftArg(0); | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -112,23 +112,6 @@ const { Readable } = require('stream'); | |||
| 112 | 112 | ||
| 113 | 113 | } | |
| 114 | 114 | ||
| 115 | - { | ||
| 116 | - // Check that error is thrown for invalid chunks | ||
| 117 | - | ||
| 118 | - const readable = new Readable({ read() {} }); | ||
| 119 | - function checkError(fn) { | ||
| 120 | - common.expectsError(fn, { | ||
| 121 | - code: 'ERR_INVALID_ARG_TYPE', | ||
| 122 | - type: TypeError | ||
| 123 | - }); | ||
| 124 | - } | ||
| 125 | - | ||
| 126 | - checkError(() => readable.unshift([])); | ||
| 127 | - checkError(() => readable.unshift({})); | ||
| 128 | - checkError(() => readable.unshift(0)); | ||
| 129 | - | ||
| 130 | - } | ||
| 131 | - | ||
| 132 | 115 | { | |
| 133 | 116 | // Check that ObjectMode works | |
| 134 | 117 | const readable = new Readable({ objectMode: true, read() {} }); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -86,13 +86,7 @@ w._write = function(chunk, encoding, cb) { | |||
| 86 | 86 | }; | |
| 87 | 87 | ||
| 88 | 88 | r.on('end', common.mustCall(function() { | |
| 89 | - common.expectsError(function() { | ||
| 90 | - r.unshift(Buffer.allocUnsafe(1)); | ||
| 91 | - }, { | ||
| 92 | - code: 'ERR_STREAM_UNSHIFT_AFTER_END_EVENT', | ||
| 93 | - type: Error, | ||
| 94 | - message: 'stream.unshift() after end event' | ||
| 95 | - }); | ||
| 89 | + r.unshift(Buffer.allocUnsafe(1)); | ||
| 96 | 90 | w.end(); | |
| 97 | 91 | })); | |
| 98 | 92 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -402,3 +402,42 @@ const helloWorldBuffer = Buffer.from('hello world'); | |||
| 402 | 402 | w.write(Buffer.allocUnsafe(1)); | |
| 403 | 403 | w.end(Buffer.allocUnsafe(0)); | |
| 404 | 404 | } | |
| 405 | + | ||
| 406 | + { | ||
| 407 | + // Verify that error is only emitted once when failing in _finish. | ||
| 408 | + const w = new W(); | ||
| 409 | + | ||
| 410 | + w._final = common.mustCall(function(cb) { | ||
| 411 | + cb(new Error('test')); | ||
| 412 | + }); | ||
| 413 | + w.on('error', common.mustCall((err) => { | ||
| 414 | + assert.strictEqual(w._writableState.errorEmitted, true); | ||
| 415 | + assert.strictEqual(err.message, 'test'); | ||
| 416 | + w.on('error', common.mustNotCall()); | ||
| 417 | + w.destroy(new Error()); | ||
| 418 | + })); | ||
| 419 | + w.end(); | ||
| 420 | + } | ||
| 421 | + | ||
| 422 | + { | ||
| 423 | + // Verify that error is only emitted once when failing in write. | ||
| 424 | + const w = new W(); | ||
| 425 | + w.on('error', common.mustCall((err) => { | ||
| 426 | + assert.strictEqual(w._writableState.errorEmitted, true); | ||
| 427 | + assert.strictEqual(err.code, 'ERR_STREAM_NULL_VALUES'); | ||
| 428 | + })); | ||
| 429 | + w.write(null); | ||
| 430 | + w.destroy(new Error()); | ||
| 431 | + } | ||
| 432 | + | ||
| 433 | + { | ||
| 434 | + // Verify that error is only emitted once when failing in write after end. | ||
| 435 | + const w = new W(); | ||
| 436 | + w.on('error', common.mustCall((err) => { | ||
| 437 | + assert.strictEqual(w._writableState.errorEmitted, true); | ||
| 438 | + assert.strictEqual(err.code, 'ERR_STREAM_WRITE_AFTER_END'); | ||
| 439 | + })); | ||
| 440 | + w.end(); | ||
| 441 | + w.write('hello'); | ||
| 442 | + w.destroy(new Error()); | ||
| 443 | + } | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments