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