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