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