• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict';
2const common = require('../common');
3
4// This test checks that the semantics of `util.callbackify` are as described in
5// the API docs
6
7const assert = require('assert');
8const { callbackify } = require('util');
9const { execFile } = require('child_process');
10const fixtures = require('../common/fixtures');
11
12const values = [
13  'hello world',
14  null,
15  undefined,
16  false,
17  0,
18  {},
19  { key: 'value' },
20  Symbol('I am a symbol'),
21  function ok() {},
22  ['array', 'with', 4, 'values'],
23  new Error('boo')
24];
25
26{
27  // Test that the resolution value is passed as second argument to callback
28  for (const value of values) {
29    // Test and `async function`
30    async function asyncFn() {
31      return value;
32    }
33
34    const cbAsyncFn = callbackify(asyncFn);
35    cbAsyncFn(common.mustCall((err, ret) => {
36      assert.ifError(err);
37      assert.strictEqual(ret, value);
38    }));
39
40    // Test Promise factory
41    function promiseFn() {
42      return Promise.resolve(value);
43    }
44
45    const cbPromiseFn = callbackify(promiseFn);
46    cbPromiseFn(common.mustCall((err, ret) => {
47      assert.ifError(err);
48      assert.strictEqual(ret, value);
49    }));
50
51    // Test Thenable
52    function thenableFn() {
53      return {
54        then(onRes, onRej) {
55          onRes(value);
56        }
57      };
58    }
59
60    const cbThenableFn = callbackify(thenableFn);
61    cbThenableFn(common.mustCall((err, ret) => {
62      assert.ifError(err);
63      assert.strictEqual(ret, value);
64    }));
65  }
66}
67
68{
69  // Test that rejection reason is passed as first argument to callback
70  for (const value of values) {
71    // Test an `async function`
72    async function asyncFn() {
73      return Promise.reject(value);
74    }
75
76    const cbAsyncFn = callbackify(asyncFn);
77    assert.strictEqual(cbAsyncFn.length, 1);
78    assert.strictEqual(cbAsyncFn.name, 'asyncFnCallbackified');
79    cbAsyncFn(common.mustCall((err, ret) => {
80      assert.strictEqual(ret, undefined);
81      if (err instanceof Error) {
82        if ('reason' in err) {
83          assert(!value);
84          assert.strictEqual(err.code, 'ERR_FALSY_VALUE_REJECTION');
85          assert.strictEqual(err.reason, value);
86        } else {
87          assert.strictEqual(String(value).endsWith(err.message), true);
88        }
89      } else {
90        assert.strictEqual(err, value);
91      }
92    }));
93
94    // Test a Promise factory
95    function promiseFn() {
96      return Promise.reject(value);
97    }
98    const obj = {};
99    Object.defineProperty(promiseFn, 'name', {
100      value: obj,
101      writable: false,
102      enumerable: false,
103      configurable: true
104    });
105
106    const cbPromiseFn = callbackify(promiseFn);
107    assert.strictEqual(promiseFn.name, obj);
108    cbPromiseFn(common.mustCall((err, ret) => {
109      assert.strictEqual(ret, undefined);
110      if (err instanceof Error) {
111        if ('reason' in err) {
112          assert(!value);
113          assert.strictEqual(err.code, 'ERR_FALSY_VALUE_REJECTION');
114          assert.strictEqual(err.reason, value);
115        } else {
116          assert.strictEqual(String(value).endsWith(err.message), true);
117        }
118      } else {
119        assert.strictEqual(err, value);
120      }
121    }));
122
123    // Test Thenable
124    function thenableFn() {
125      return {
126        then(onRes, onRej) {
127          onRej(value);
128        }
129      };
130    }
131
132    const cbThenableFn = callbackify(thenableFn);
133    cbThenableFn(common.mustCall((err, ret) => {
134      assert.strictEqual(ret, undefined);
135      if (err instanceof Error) {
136        if ('reason' in err) {
137          assert(!value);
138          assert.strictEqual(err.code, 'ERR_FALSY_VALUE_REJECTION');
139          assert.strictEqual(err.reason, value);
140        } else {
141          assert.strictEqual(String(value).endsWith(err.message), true);
142        }
143      } else {
144        assert.strictEqual(err, value);
145      }
146    }));
147  }
148}
149
150{
151  // Test that arguments passed to callbackified function are passed to original
152  for (const value of values) {
153    async function asyncFn(arg) {
154      assert.strictEqual(arg, value);
155      return arg;
156    }
157
158    const cbAsyncFn = callbackify(asyncFn);
159    assert.strictEqual(cbAsyncFn.length, 2);
160    assert.notStrictEqual(
161      Object.getPrototypeOf(cbAsyncFn),
162      Object.getPrototypeOf(asyncFn)
163    );
164    assert.strictEqual(Object.getPrototypeOf(cbAsyncFn), Function.prototype);
165    cbAsyncFn(value, common.mustCall((err, ret) => {
166      assert.ifError(err);
167      assert.strictEqual(ret, value);
168    }));
169
170    function promiseFn(arg) {
171      assert.strictEqual(arg, value);
172      return Promise.resolve(arg);
173    }
174    const obj = {};
175    Object.defineProperty(promiseFn, 'length', {
176      value: obj,
177      writable: false,
178      enumerable: false,
179      configurable: true
180    });
181
182    const cbPromiseFn = callbackify(promiseFn);
183    assert.strictEqual(promiseFn.length, obj);
184    cbPromiseFn(value, common.mustCall((err, ret) => {
185      assert.ifError(err);
186      assert.strictEqual(ret, value);
187    }));
188  }
189}
190
191{
192  // Test that `this` binding is the same for callbackified and original
193  for (const value of values) {
194    const iAmThis = {
195      fn(arg) {
196        assert.strictEqual(this, iAmThis);
197        return Promise.resolve(arg);
198      },
199    };
200    iAmThis.cbFn = callbackify(iAmThis.fn);
201    iAmThis.cbFn(value, common.mustCall(function(err, ret) {
202      assert.ifError(err);
203      assert.strictEqual(ret, value);
204      assert.strictEqual(this, iAmThis);
205    }));
206
207    const iAmThat = {
208      async fn(arg) {
209        assert.strictEqual(this, iAmThat);
210        return arg;
211      },
212    };
213    iAmThat.cbFn = callbackify(iAmThat.fn);
214    iAmThat.cbFn(value, common.mustCall(function(err, ret) {
215      assert.ifError(err);
216      assert.strictEqual(ret, value);
217      assert.strictEqual(this, iAmThat);
218    }));
219  }
220}
221
222{
223  // Test that callback that throws emits an `uncaughtException` event
224  const fixture = fixtures.path('uncaught-exceptions', 'callbackify1.js');
225  execFile(
226    process.execPath,
227    [fixture],
228    common.mustCall((err, stdout, stderr) => {
229      assert.strictEqual(err.code, 1);
230      assert.strictEqual(Object.getPrototypeOf(err).name, 'Error');
231      assert.strictEqual(stdout, '');
232      const errLines = stderr.trim().split(/[\r\n]+/);
233      const errLine = errLines.find((l) => /^Error/.exec(l));
234      assert.strictEqual(errLine, `Error: ${fixture}`);
235    })
236  );
237}
238
239{
240  // Test that handled `uncaughtException` works and passes rejection reason
241  const fixture = fixtures.path('uncaught-exceptions', 'callbackify2.js');
242  execFile(
243    process.execPath,
244    [fixture],
245    common.mustCall((err, stdout, stderr) => {
246      assert.ifError(err);
247      assert.strictEqual(
248        stdout.trim(),
249        `ifError got unwanted exception: ${fixture}`);
250      assert.strictEqual(stderr, '');
251    })
252  );
253}
254
255{
256  // Verify that non-function inputs throw.
257  ['foo', null, undefined, false, 0, {}, Symbol(), []].forEach((value) => {
258    assert.throws(() => {
259      callbackify(value);
260    }, {
261      code: 'ERR_INVALID_ARG_TYPE',
262      name: 'TypeError',
263      message: 'The "original" argument must be of type function.' +
264               common.invalidArgTypeHelper(value)
265    });
266  });
267}
268
269{
270  async function asyncFn() {
271    return 42;
272  }
273
274  const cb = callbackify(asyncFn);
275  const args = [];
276
277  // Verify that the last argument to the callbackified function is a function.
278  ['foo', null, undefined, false, 0, {}, Symbol(), []].forEach((value) => {
279    args.push(value);
280    assert.throws(() => {
281      cb(...args);
282    }, {
283      code: 'ERR_INVALID_ARG_TYPE',
284      name: 'TypeError',
285      message: 'The last argument must be of type function.' +
286               common.invalidArgTypeHelper(value)
287    });
288  });
289}
290