Skip to content

Commit 4cb5f3d

Browse files
committed
fs: throw on invalid callbacks for async functions
If an asynchronous function is passed no callback function, there is no way to return the result. This patch throws an error if the callback passed is not valid or none passed at all. PR-URL: #12562 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Anna Henningsen <anna@addaleax.net>
1 parent e429f9a commit 4cb5f3d

11 files changed

+180
-104
lines changed

doc/api/deprecations.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -150,9 +150,10 @@ 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: Runtime
153+
Type: End-of-Life
154154

155-
Calling an asynchronous function without a callback is deprecated.
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)
156157

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

doc/api/fs.md

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

lib/fs.js

+16-44
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,6 @@ 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);
6261
const errnoException = util._errnoException;
6362

6463
function getOptions(options, defaultOptions) {
@@ -88,48 +87,26 @@ function copyObject(source) {
8887
return target;
8988
}
9089

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-
};
90+
var internalErrors;
91+
function lazyErrors() {
92+
if (!internalErrors)
93+
internalErrors = require('internal/errors');
94+
return internalErrors;
11695
}
11796

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

122104
// Ensure that callbacks run in the global context. Only use this function
123105
// for callbacks that are passed to the binding layer, callbacks that are
124106
// invoked from JS already run in the proper scope.
125107
function makeCallback(cb) {
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-
}
108+
if (typeof cb !== 'function')
109+
throw new (lazyErrors().TypeError)('ERR_INVALID_CALLBACK');
133110

134111
return function() {
135112
return cb.apply(null, arguments);
@@ -140,13 +117,8 @@ function makeCallback(cb) {
140117
// an optimization, since the data passed back to the callback needs to be
141118
// transformed anyway.
142119
function makeStatsCallback(cb) {
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-
}
120+
if (typeof cb !== 'function')
121+
throw new (lazyErrors().TypeError)('ERR_INVALID_CALLBACK');
150122

151123
return function(err) {
152124
if (err) return cb(err);
@@ -268,10 +240,10 @@ fs.access = function(path, mode, callback) {
268240
if (typeof mode === 'function') {
269241
callback = mode;
270242
mode = fs.F_OK;
271-
} else if (typeof callback !== 'function') {
272-
throw new TypeError('"callback" argument must be a function');
273243
}
274244

245+
callback = makeCallback(callback);
246+
275247
if (handleError((path = getPathFromURL(path)), callback))
276248
return;
277249

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

281253
mode = mode | 0;
282254
var req = new FSReqWrap();
283-
req.oncomplete = makeCallback(callback);
255+
req.oncomplete = callback;
284256
binding.access(pathModule._makeLong(path), mode, req);
285257
};
286258

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').readFile('/'); // throws EISDIR
22+
require('fs').readFileSync('/'); // 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-
}, /^TypeError: "callback" argument must be a function$/);
90+
}, common.expectsError({code: 'ERR_INVALID_CALLBACK'}));
9191

9292
assert.throws(() => {
9393
fs.access(__filename, fs.F_OK, {});
94-
}, /^TypeError: "callback" argument must be a function$/);
94+
}, common.expectsError({code: 'ERR_INVALID_CALLBACK'}));
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();
26+
fs.link(undefined, undefined, common.mustNotCall());
2727
},
2828
/src must be a string or Buffer/
2929
);
3030

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

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

+1-7
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,10 @@
22
const common = require('../common');
33
const assert = require('assert');
44
const fs = require('fs');
5-
const cbTypeError = /^TypeError: "callback" argument must be a function$/;
5+
const cbTypeError = common.expectsError({code: 'ERR_INVALID_CALLBACK'});
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.';
109

1110
common.refreshTmpDir();
1211

@@ -17,11 +16,6 @@ function testMakeCallback(cb) {
1716
};
1817
}
1918

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

test/parallel/test-fs-makeStatsCallback.js

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

98
function testMakeStatsCallback(cb) {
109
return function() {
@@ -13,14 +12,9 @@ function testMakeStatsCallback(cb) {
1312
};
1413
}
1514

16-
common.expectWarning('DeprecationWarning', warn);
17-
1815
// Verify the case where a callback function is provided
1916
assert.doesNotThrow(testMakeStatsCallback(common.noop));
2017

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

test/parallel/test-fs-mkdtemp.js

-5
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,3 @@ 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-
require('../common');
2+
const common = 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);
7+
fs.write(null, Buffer.allocUnsafe(1), 0, 1, common.mustNotCall());
88
}, /TypeError/);
99

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

0 commit comments

Comments
 (0)