Skip to content

Commit 8250bfd

Browse files
committed
fs: Revert throw on invalid callbacks
This reverts 4cb5f3d Based on community feedback I think we should consider reverting this change. We should explore how this could be solved via linting rules. Refs: #12562 PR-URL: #12976 Reviewed-By: Sakthipriyan Vairamani <[email protected]> Reviewed-By: Evan Lucas <[email protected]> Reviewed-By: Colin Ihrig <[email protected]> Reviewed-By: Rich Trott <[email protected]>
1 parent c60a7fa commit 8250bfd

11 files changed

+104
-180
lines changed

doc/api/deprecations.md

+2-3
Original file line numberDiff line numberDiff line change
@@ -150,10 +150,9 @@ explicitly via error event handlers set on the domain instead.
150150
<a id="DEP0013"></a>
151151
### DEP0013: fs async function without callback
152152

153-
Type: End-of-Life
153+
Type: Runtime
154154

155-
Calling an asynchronous function without a callback will throw a `TypeError`
156-
v8.0.0 onwards. Refer: [PR 12562](https://github.com/nodejs/node/pull/12562)
155+
Calling an asynchronous function without a callback is deprecated.
157156

158157
<a id="DEP0014"></a>
159158
### DEP0014: fs.read legacy String interface

doc/api/fs.md

+30-150
Large diffs are not rendered by default.

lib/fs.js

+44-16
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ const kMaxLength = require('buffer').kMaxLength;
5858

5959
const isWindows = process.platform === 'win32';
6060

61+
const DEBUG = process.env.NODE_DEBUG && /fs/.test(process.env.NODE_DEBUG);
6162
const errnoException = util._errnoException;
6263

6364
function getOptions(options, defaultOptions) {
@@ -87,26 +88,48 @@ function copyObject(source) {
8788
return target;
8889
}
8990

90-
var internalErrors;
91-
function lazyErrors() {
92-
if (!internalErrors)
93-
internalErrors = require('internal/errors');
94-
return internalErrors;
91+
function rethrow() {
92+
// TODO(thefourtheye) Throw error instead of warning in major version > 7
93+
process.emitWarning(
94+
'Calling an asynchronous function without callback is deprecated.',
95+
'DeprecationWarning', 'DEP0013', rethrow
96+
);
97+
98+
// Only enable in debug mode. A backtrace uses ~1000 bytes of heap space and
99+
// is fairly slow to generate.
100+
if (DEBUG) {
101+
var backtrace = new Error();
102+
return function(err) {
103+
if (err) {
104+
backtrace.stack = err.name + ': ' + err.message +
105+
backtrace.stack.substr(backtrace.name.length);
106+
throw backtrace;
107+
}
108+
};
109+
}
110+
111+
return function(err) {
112+
if (err) {
113+
throw err; // Forgot a callback but don't know where? Use NODE_DEBUG=fs
114+
}
115+
};
95116
}
96117

97118
function maybeCallback(cb) {
98-
if (typeof cb === 'function')
99-
return cb;
100-
else
101-
throw new (lazyErrors().TypeError)('ERR_INVALID_CALLBACK');
119+
return typeof cb === 'function' ? cb : rethrow();
102120
}
103121

104122
// Ensure that callbacks run in the global context. Only use this function
105123
// for callbacks that are passed to the binding layer, callbacks that are
106124
// invoked from JS already run in the proper scope.
107125
function makeCallback(cb) {
108-
if (typeof cb !== 'function')
109-
throw new (lazyErrors().TypeError)('ERR_INVALID_CALLBACK');
126+
if (cb === undefined) {
127+
return rethrow();
128+
}
129+
130+
if (typeof cb !== 'function') {
131+
throw new TypeError('"callback" argument must be a function');
132+
}
110133

111134
return function() {
112135
return cb.apply(null, arguments);
@@ -117,8 +140,13 @@ function makeCallback(cb) {
117140
// an optimization, since the data passed back to the callback needs to be
118141
// transformed anyway.
119142
function makeStatsCallback(cb) {
120-
if (typeof cb !== 'function')
121-
throw new (lazyErrors().TypeError)('ERR_INVALID_CALLBACK');
143+
if (cb === undefined) {
144+
return rethrow();
145+
}
146+
147+
if (typeof cb !== 'function') {
148+
throw new TypeError('"callback" argument must be a function');
149+
}
122150

123151
return function(err) {
124152
if (err) return cb(err);
@@ -240,10 +268,10 @@ fs.access = function(path, mode, callback) {
240268
if (typeof mode === 'function') {
241269
callback = mode;
242270
mode = fs.F_OK;
271+
} else if (typeof callback !== 'function') {
272+
throw new TypeError('"callback" argument must be a function');
243273
}
244274

245-
callback = makeCallback(callback);
246-
247275
if (handleError((path = getPathFromURL(path)), callback))
248276
return;
249277

@@ -252,7 +280,7 @@ fs.access = function(path, mode, callback) {
252280

253281
mode = mode | 0;
254282
var req = new FSReqWrap();
255-
req.oncomplete = callback;
283+
req.oncomplete = makeCallback(callback);
256284
binding.access(pathModule._makeLong(path), mode, req);
257285
};
258286

test/fixtures/test-fs-readfile-error.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,4 @@
1919
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
2020
// USE OR OTHER DEALINGS IN THE SOFTWARE.
2121

22-
require('fs').readFileSync('/'); // throws EISDIR
22+
require('fs').readFile('/'); // throws EISDIR

test/parallel/test-fs-access.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -87,11 +87,11 @@ assert.throws(() => {
8787

8888
assert.throws(() => {
8989
fs.access(__filename, fs.F_OK);
90-
}, common.expectsError({code: 'ERR_INVALID_CALLBACK'}));
90+
}, /^TypeError: "callback" argument must be a function$/);
9191

9292
assert.throws(() => {
9393
fs.access(__filename, fs.F_OK, {});
94-
}, common.expectsError({code: 'ERR_INVALID_CALLBACK'}));
94+
}, /^TypeError: "callback" argument must be a function$/);
9595

9696
assert.doesNotThrow(() => {
9797
fs.accessSync(__filename);

test/parallel/test-fs-link.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,14 @@ fs.link(srcPath, dstPath, common.mustCall(callback));
2323

2424
assert.throws(
2525
function() {
26-
fs.link(undefined, undefined, common.mustNotCall());
26+
fs.link();
2727
},
2828
/src must be a string or Buffer/
2929
);
3030

3131
assert.throws(
3232
function() {
33-
fs.link('abc', undefined, common.mustNotCall());
33+
fs.link('abc');
3434
},
3535
/dest must be a string or Buffer/
3636
);

test/parallel/test-fs-make-callback.js

+7-1
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@
22
const common = require('../common');
33
const assert = require('assert');
44
const fs = require('fs');
5-
const cbTypeError = common.expectsError({code: 'ERR_INVALID_CALLBACK'});
5+
const cbTypeError = /^TypeError: "callback" argument must be a function$/;
66
const callbackThrowValues = [null, true, false, 0, 1, 'foo', /foo/, [], {}];
77

88
const { sep } = require('path');
9+
const warn = 'Calling an asynchronous function without callback is deprecated.';
910

1011
common.refreshTmpDir();
1112

@@ -16,6 +17,11 @@ function testMakeCallback(cb) {
1617
};
1718
}
1819

20+
common.expectWarning('DeprecationWarning', warn);
21+
22+
// Passing undefined/nothing calls rethrow() internally, which emits a warning
23+
assert.doesNotThrow(testMakeCallback());
24+
1925
function invalidCallbackThrowsTests() {
2026
callbackThrowValues.forEach((value) => {
2127
assert.throws(testMakeCallback(value), cbTypeError);

test/parallel/test-fs-makeStatsCallback.js

+7-1
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22
const common = require('../common');
33
const assert = require('assert');
44
const fs = require('fs');
5-
const cbTypeError = common.expectsError({code: 'ERR_INVALID_CALLBACK'});
5+
const cbTypeError = /^TypeError: "callback" argument must be a function$/;
66
const callbackThrowValues = [null, true, false, 0, 1, 'foo', /foo/, [], {}];
7+
const warn = 'Calling an asynchronous function without callback is deprecated.';
78

89
function testMakeStatsCallback(cb) {
910
return function() {
@@ -12,9 +13,14 @@ function testMakeStatsCallback(cb) {
1213
};
1314
}
1415

16+
common.expectWarning('DeprecationWarning', warn);
17+
1518
// Verify the case where a callback function is provided
1619
assert.doesNotThrow(testMakeStatsCallback(common.noop));
1720

21+
// Passing undefined/nothing calls rethrow() internally, which emits a warning
22+
assert.doesNotThrow(testMakeStatsCallback());
23+
1824
function invalidCallbackThrowsTests() {
1925
callbackThrowValues.forEach((value) => {
2026
assert.throws(testMakeStatsCallback(value), cbTypeError);

test/parallel/test-fs-mkdtemp.js

+5
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,8 @@ fs.mkdtemp(path.join(common.tmpDir, 'bar.'), common.mustCall(handler));
2929
// Same test as above, but making sure that passing an options object doesn't
3030
// affect the way the callback function is handled.
3131
fs.mkdtemp(path.join(common.tmpDir, 'bar.'), {}, common.mustCall(handler));
32+
33+
// Making sure that not passing a callback doesn't crash, as a default function
34+
// is passed internally.
35+
assert.doesNotThrow(() => fs.mkdtemp(path.join(common.tmpDir, 'bar-')));
36+
assert.doesNotThrow(() => fs.mkdtemp(path.join(common.tmpDir, 'bar-'), {}));

test/parallel/test-fs-readfile-error.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ function test(env, cb) {
4646

4747
test({ NODE_DEBUG: '' }, common.mustCall((data) => {
4848
assert(/EISDIR/.test(data));
49-
assert(/test-fs-readfile-error/.test(data));
49+
assert(!/test-fs-readfile-error/.test(data));
5050
}));
5151

5252
test({ NODE_DEBUG: 'fs' }, common.mustCall((data) => {

test/parallel/test-fs-write-no-fd.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
'use strict';
2-
const common = require('../common');
2+
require('../common');
33
const fs = require('fs');
44
const assert = require('assert');
55

66
assert.throws(function() {
7-
fs.write(null, Buffer.allocUnsafe(1), 0, 1, common.mustNotCall());
7+
fs.write(null, Buffer.allocUnsafe(1), 0, 1);
88
}, /TypeError/);
99

1010
assert.throws(function() {
11-
fs.write(null, '1', 0, 1, common.mustNotCall());
11+
fs.write(null, '1', 0, 1);
1212
}, /TypeError/);

0 commit comments

Comments
 (0)