promisify-node
Advanced tools
Comparing version 0.3.0 to 0.4.0
110
index.js
const Promise = require("nodegit-promise"); | ||
const args = require("./utils/args"); | ||
const cloneFunction = require("./utils/cloneFunction"); | ||
const objectAssign = require("object-assign"); | ||
@@ -15,13 +17,29 @@ // Unfortunately this list is not exhaustive, so if you find that a method does | ||
* @param {String} parentKeyName - Tracks the keyName in a digestable format. | ||
* @param {Boolean} noMutate - if set to true then all reference properties are | ||
* cloned to avoid mutating the original object. | ||
* @returns {*} exports - Identity. | ||
*/ | ||
function processExports(exports, test, cached, parentKeyName) { | ||
// Return early if this object has already been processed. | ||
if (cached.indexOf(exports) > -1) { | ||
function processExports(exports, test, cached, parentKeyName, noMutate) { | ||
if(!exports) { | ||
return exports; | ||
} | ||
// Record this object in the cache. | ||
cached.push(exports); | ||
if(noMutate || typeof exports === "function") { | ||
// When not mutating we have to cache the original and the wrapped clone. | ||
var cacheResult = cached.filter(function(c) { return c.original === exports; }); | ||
if(cacheResult.length) { | ||
return cacheResult[0].wrapped; | ||
} | ||
} else { | ||
// Return early if this object has already been processed. | ||
if (cached.indexOf(exports) > -1) { | ||
return exports; | ||
} | ||
} | ||
// Record this object in the cache, if it is not a function. | ||
if(typeof exports != "function") { | ||
cached.push(exports); | ||
} | ||
// Pass through if not an object or function. | ||
@@ -33,23 +51,49 @@ if (typeof exports != "object" && typeof exports != "function") { | ||
var name = exports.name + "#"; | ||
var target; | ||
// If a function, simply return it wrapped. | ||
if (typeof exports === "function") { | ||
var wrapped = exports; | ||
var isAsyncFunction = false; | ||
// Check the callback either passes the test function, or accepts a callback. | ||
if ((test && test(exports, exports.name, parentKeyName)) | ||
// If the callback name exists as the last argument, consider it an | ||
// asynchronous function. Brittle? Fragile? Effective. | ||
|| (callbacks.indexOf(args(exports).slice(-1)[0]) > -1)) { | ||
// Assign the new function in place. | ||
wrapped = Promise.denodeify(exports); | ||
isAsyncFunction = true; | ||
} else if(noMutate) { | ||
// If not mutating, then we need to clone the function, even though it isn't async. | ||
wrapped = cloneFunction(exports); | ||
} | ||
// Set which object we'll mutate based upon the noMutate flag. | ||
target = noMutate ? wrapped : exports; | ||
// Here we can push our cloned/wrapped function and original onto cache. | ||
cached.push({ | ||
original: exports, | ||
wrapped: wrapped | ||
}); | ||
// Find properties added to functions. | ||
for (var keyName in exports) { | ||
exports[keyName] = processExports(exports[keyName], test, cached, name); | ||
target[keyName] = processExports(exports[keyName], test, cached, name, noMutate); | ||
} | ||
// Assign the new function in place. | ||
var wrapped = Promise.denodeify(exports); | ||
// Find methods on the prototype, if there are any. | ||
if (Object.keys(exports.prototype).length) { | ||
processExports(exports.prototype, test, cached, name); | ||
// Attach the augmented prototype. | ||
wrapped.prototype = processExports(exports.prototype, test, cached, name, noMutate); | ||
} | ||
// Attach the augmented prototype. | ||
wrapped.prototype = exports.prototype; | ||
// Ensure attached properties to the previous function are accessible. | ||
wrapped.__proto__ = exports; | ||
// Only do this if it's an async (wrapped) function, else we're setting | ||
// __proto__ to itself, which isn't allowed. | ||
if(isAsyncFunction) { | ||
wrapped.__proto__ = exports; | ||
} | ||
@@ -59,5 +103,16 @@ return wrapped; | ||
Object.keys(exports).map(function(keyName) { | ||
// Make a shallow clone if we're not mutating and set it as the target, else just use exports | ||
target = noMutate ? objectAssign({}, exports) : exports; | ||
// We have our shallow cloned object, so put it (and the original) in the cache | ||
if(noMutate) { | ||
cached.push({ | ||
original: exports, | ||
wrapped: target | ||
}); | ||
} | ||
Object.keys(target).map(function(keyName) { | ||
// Convert to values. | ||
return [keyName, exports[keyName]]; | ||
return [keyName, target[keyName]]; | ||
}).filter(function(keyVal) { | ||
@@ -69,6 +124,4 @@ var keyName = keyVal[0]; | ||
if (typeof value === "object") { | ||
processExports(exports, test, cached, keyName + "."); | ||
} | ||
// Filter to functions with callbacks only. | ||
else if (typeof value === "function") { | ||
processExports(value, test, cached, keyName + ".", noMutate); | ||
} else if (typeof value === "function") { | ||
// If a filter function exists, use this to determine if the function | ||
@@ -81,7 +134,3 @@ // is asynchronous. | ||
// If the callback name exists as the last argument, consider it an | ||
// asynchronous function. Brittle? Fragile? Effective. | ||
if (callbacks.indexOf(args(value).slice(-1)[0]) > -1) { | ||
return true; | ||
} | ||
return true; | ||
} | ||
@@ -93,6 +142,6 @@ }).forEach(function(keyVal) { | ||
// Wrap this function and reassign. | ||
exports[keyName] = processExports(func, test, cached, parentKeyName); | ||
target[keyName] = processExports(func, test, cached, parentKeyName, noMutate); | ||
}); | ||
return exports; | ||
return target; | ||
} | ||
@@ -105,5 +154,6 @@ | ||
* @param {Function} test - Optional function to identify async methods. | ||
* @param {Boolean} noMutate - Optional set to true to avoid mutating the target. | ||
* @returns {*} exports - The resolved value from require or passed in value. | ||
*/ | ||
module.exports = function(name, test) { | ||
module.exports = function(name, test, noMutate) { | ||
var exports = name; | ||
@@ -115,2 +165,4 @@ | ||
exports = require(name); | ||
// Unless explicitly overridden, don't mutate when requiring modules. | ||
noMutate = !(noMutate === false); | ||
} | ||
@@ -120,3 +172,3 @@ | ||
// promises. | ||
return processExports(exports, test, []); | ||
return processExports(exports, test, [], undefined, noMutate); | ||
}; | ||
@@ -123,0 +175,0 @@ |
{ | ||
"name": "promisify-node", | ||
"version": "0.3.0", | ||
"version": "0.4.0", | ||
"description": "Wrap Node-callback functions to return Promises.", | ||
"main": "index.js", | ||
"dependencies": { | ||
"nodegit-promise": "~4.0.0" | ||
"nodegit-promise": "~4.0.0", | ||
"object-assign": "^4.0.1" | ||
}, | ||
@@ -9,0 +10,0 @@ "devDependencies": { |
@@ -73,2 +73,28 @@ Promisify Node | ||
Wrap without mutating the original: | ||
```javascript | ||
var promisify = require("promisify-node"); | ||
var myObj = { | ||
myMethod: function(a, b, cb) { | ||
cb(a, b); | ||
} | ||
}; | ||
// Store the original method to check later | ||
var originalMethod = myObj.myMethod; | ||
// Now store the result, since the 'true' value means it won't mutate 'myObj'. | ||
var promisifiedObj = promisify(myObj, undefined, true); | ||
// Intentionally cause a failure by passing an object and inspect the message. | ||
promisifiedObj.myMethod({ msg: "Failure!" }, null).then(null, function(err) { | ||
console.log(err.msg); | ||
}); | ||
// The original method is still intact | ||
assert(myObj.myMethod === originalMethod); | ||
assert(promisifiedObj.myMethod !== myObj.myMethod); | ||
``` | ||
### Tests ### | ||
@@ -75,0 +101,0 @@ |
var promisify = require("../"); | ||
var assert = require("assert"); | ||
var assert = require("assert"), | ||
fsOriginal = require('fs'); | ||
describe("Promisify", function() { | ||
function isPromisified(fn, ctx) { | ||
var result = fn && fn.apply(ctx, Array.prototype.slice.call(arguments, 2)); | ||
return result && (typeof result.then === 'function'); | ||
} | ||
it("can convert a basic async function", function() { | ||
@@ -33,2 +39,37 @@ function test(cb) { | ||
}); | ||
it("doesn't mutate objects for other consumers", function() { | ||
var fsp = promisify("fs"); | ||
var fs2 = require("fs"); | ||
assert(fsOriginal.readFile !== fsp.readFile, "pre-required mutated"); | ||
assert(fsOriginal.readFile === fs2.readFile, "post-required mutated"); | ||
assert(fsp.readFile !== fs2.readFile, "post-required mutated"); | ||
}); | ||
it("doesn't mutate functions for other consumers", function() { | ||
var fn = require(__dirname + "/examples/fn-export.js"); | ||
var fnx = fn.x; | ||
var fnp = promisify(__dirname + "/examples/fn-export.js"); | ||
var fn2 = require(__dirname + "/examples/fn-export.js"); | ||
assert(fn.x !== fnp, "pre-required mutated"); | ||
assert(fn2.x !== fnp, "post-required mutated"); | ||
assert(fn.x === fnx, "function property mutated"); | ||
assert(fnp.x !== fn, "function property not replaced"); | ||
}); | ||
it("doesn't mutate prototypes for other consumers", function() { | ||
var A = require(__dirname + "/examples/proto-export.js"); | ||
var a = new A(5); | ||
var Ap = promisify(__dirname + "/examples/proto-export.js"); | ||
var ap = new Ap(5); | ||
var A2 = require(__dirname + "/examples/proto-export.js"); | ||
var a2 = new A2(5); | ||
assert(isPromisified(ap.a, ap), "prototype method not promisified"); | ||
assert(a.a !== ap.a, "pre-required mutated"); | ||
assert(a2.a !== ap.a, "post-required mutated"); | ||
assert(a2.a === a.a, "post-required mutated"); | ||
}); | ||
}); | ||
@@ -108,3 +149,75 @@ | ||
}); | ||
it("can deal with the same async function being present in an object more than once", function() { | ||
var a = { | ||
a: function(d, cb) { | ||
cb && cb(null, d); | ||
} | ||
}; | ||
a.b = a.a; | ||
a = promisify(a); | ||
assert(a.a(5) !== undefined, "function not wrapped"); | ||
assert(typeof a.a(5).then === "function", "function not wrapped"); | ||
assert(a.b(5) !== undefined, "duplicate function not wrapped"); | ||
assert(typeof a.b(5).then === "function", "duplicate function not wrapped"); | ||
}); | ||
it("can deal with cyclical function properties", function() { | ||
var a = function(d, cb) { | ||
cb && cb(null, d); | ||
}; | ||
a.a = a; | ||
a = promisify(a); | ||
assert(a(5) !== undefined, "function not wrapped"); | ||
assert(typeof a(5).then === "function", "function not wrapped"); | ||
assert(a.a(5) !== undefined, "function property not wrapped"); | ||
assert(typeof a.a(5).then === "function", "function property not wrapped"); | ||
}); | ||
}); | ||
describe("no mutate", function() { | ||
it("can promisify an object without mutating it", function() { | ||
var a = { | ||
a: function(cb) { cb(); } | ||
}; | ||
var b = promisify(a, undefined, true); | ||
assert(isPromisified(b.a, b), "method not promisified"); | ||
assert(a.a !== b.a, "object mutated"); | ||
}); | ||
it("can promisify a function's properties without mutating it", function() { | ||
var a = function(cb){ cb(null, 1); }; | ||
a.a = function(cb) { cb(); }; | ||
var b = promisify(a, undefined, true); | ||
assert(isPromisified(b), "method not promisified"); | ||
assert(isPromisified(b.a, b), "method property not promisified"); | ||
assert(a.a !== b, "method property mutated"); | ||
assert(a.a !== b.a, "method property mutated"); | ||
}); | ||
it("can promisify a constructor without mutating it", function() { | ||
var A = function(){ }; | ||
A.a = function(cb) { cb(); }; | ||
A.prototype.a = function(cb) { cb(null, 2); }; | ||
var B = promisify(A, undefined, true); | ||
var b = new B(); | ||
assert(isPromisified(B.a, B), "method property not promisified"); | ||
assert(isPromisified(b.a, b), "prototype method not promisified"); | ||
assert(A.a !== B.a, "method property mutated"); | ||
assert(A.prototype.a !== b.a, "prototype mutated"); | ||
}); | ||
}); | ||
}); |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
17961
11
354
106
2
7
+ Addedobject-assign@^4.0.1
+ Addedobject-assign@4.1.1(transitive)