diff options
author | Refael Ackermann <refack@gmail.com> | 2017-04-27 21:57:12 -0400 |
---|---|---|
committer | Refael Ackermann <refack@gmail.com> | 2017-06-10 22:49:28 -0400 |
commit | af3aa682ac534bb55765f5fef2755a88e5ff2580 (patch) | |
tree | d632fb6f8347f58c05731d16374b05706ef828ba /lib | |
parent | 780acc2208a3cdd3b01ae93aeaa478771fd3fd56 (diff) | |
download | android-node-v8-af3aa682ac534bb55765f5fef2755a88e5ff2580.tar.gz android-node-v8-af3aa682ac534bb55765f5fef2755a88e5ff2580.tar.bz2 android-node-v8-af3aa682ac534bb55765f5fef2755a88e5ff2580.zip |
util: add callbackify
Add `util.callbackify(function)` for creating callback style functions
from functions returning a `Thenable`
PR-URL: https://github.com/nodejs/node/pull/12712
Fixes: https://github.com/nodejs/CTC/issues/109
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Reviewed-By: Teddy Katz <teddy.katz@gmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: Timothy Gu <timothygu99@gmail.com>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Diffstat (limited to 'lib')
-rw-r--r-- | lib/internal/errors.js | 1 | ||||
-rw-r--r-- | lib/util.js | 50 |
2 files changed, 51 insertions, 0 deletions
diff --git a/lib/internal/errors.js b/lib/internal/errors.js index 57e73ac56a..1f1dd82c37 100644 --- a/lib/internal/errors.js +++ b/lib/internal/errors.js @@ -164,6 +164,7 @@ E('ERR_SOCKET_BAD_PORT', 'Port should be > 0 and < 65536'); E('ERR_SOCKET_DGRAM_NOT_RUNNING', 'Not running'); E('ERR_V8BREAKITERATOR', 'full ICU data not installed. ' + 'See https://github.com/nodejs/node/wiki/Intl'); +E('FALSY_VALUE_REJECTION', 'Promise was rejected with falsy value'); // Add new errors from here... function invalidArgType(name, expected, actual) { diff --git a/lib/util.js b/lib/util.js index 3c31e0932a..11dd521668 100644 --- a/lib/util.js +++ b/lib/util.js @@ -1047,3 +1047,53 @@ process.versions[exports.inspect.custom] = (depth) => exports.format(JSON.parse(JSON.stringify(process.versions))); exports.promisify = internalUtil.promisify; + +function callbackifyOnRejected(reason, cb) { + // `!reason` guard inspired by bluebird (Ref: https://goo.gl/t5IS6M). + // Because `null` is a special error value in callbacks which means "no error + // occurred", we error-wrap so the callback consumer can distinguish between + // "the promise rejected with null" or "the promise fulfilled with undefined". + if (!reason) { + const newReason = new errors.Error('FALSY_VALUE_REJECTION'); + newReason.reason = reason; + reason = newReason; + Error.captureStackTrace(reason, callbackifyOnRejected); + } + return cb(reason); +} + + +function callbackify(original) { + if (typeof original !== 'function') { + throw new errors.TypeError( + 'ERR_INVALID_ARG_TYPE', + 'original', + 'function'); + } + + // We DO NOT return the promise as it gives the user a false sense that + // the promise is actually somehow related to the callback's execution + // and that the callback throwing will reject the promise. + function callbackified(...args) { + const maybeCb = args.pop(); + if (typeof maybeCb !== 'function') { + throw new errors.TypeError( + 'ERR_INVALID_ARG_TYPE', + 'last argument', + 'function'); + } + const cb = (...args) => { Reflect.apply(maybeCb, this, args); }; + // In true node style we process the callback on `nextTick` with all the + // implications (stack, `uncaughtException`, `async_hooks`) + Reflect.apply(original, this, args) + .then((ret) => process.nextTick(cb, null, ret), + (rej) => process.nextTick(callbackifyOnRejected, rej, cb)); + } + + Object.setPrototypeOf(callbackified, Object.getPrototypeOf(original)); + Object.defineProperties(callbackified, + Object.getOwnPropertyDescriptors(original)); + return callbackified; +} + +exports.callbackify = callbackify; |