You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
245 lines
6.3 KiB
245 lines
6.3 KiB
import Promise from './promise'; |
|
import { |
|
noop, |
|
resolve, |
|
reject, |
|
} from './-internal'; |
|
|
|
function makeObject(_, argumentNames) { |
|
let obj = {}; |
|
let length = _.length; |
|
let args = new Array(length); |
|
|
|
for (let x = 0; x < length; x++) { |
|
args[x] = _[x]; |
|
} |
|
|
|
for (let i = 0; i < argumentNames.length; i++) { |
|
let name = argumentNames[i]; |
|
obj[name] = args[i + 1]; |
|
} |
|
|
|
return obj; |
|
} |
|
|
|
function arrayResult(_) { |
|
let length = _.length; |
|
let args = new Array(length - 1); |
|
|
|
for (let i = 1; i < length; i++) { |
|
args[i - 1] = _[i]; |
|
} |
|
|
|
return args; |
|
} |
|
|
|
function wrapThenable(then, promise) { |
|
return { |
|
then(onFulFillment, onRejection) { |
|
return then.call(promise, onFulFillment, onRejection); |
|
} |
|
}; |
|
} |
|
|
|
/** |
|
`denodeify` takes a 'node-style' function and returns a function that |
|
will return an `Promise`. You can use `denodeify` in Node.js or the |
|
browser when you'd prefer to use promises over using callbacks. For example, |
|
`denodeify` transforms the following: |
|
|
|
```javascript |
|
let fs = require('fs'); |
|
|
|
fs.readFile('myfile.txt', function(err, data){ |
|
if (err) return handleError(err); |
|
handleData(data); |
|
}); |
|
``` |
|
|
|
into: |
|
|
|
```javascript |
|
let fs = require('fs'); |
|
let readFile = denodeify(fs.readFile); |
|
|
|
readFile('myfile.txt').then(handleData, handleError); |
|
``` |
|
|
|
If the node function has multiple success parameters, then `denodeify` |
|
just returns the first one: |
|
|
|
```javascript |
|
let request = denodeify(require('request')); |
|
|
|
request('http://example.com').then(function(res) { |
|
// ... |
|
}); |
|
``` |
|
|
|
However, if you need all success parameters, setting `denodeify`'s |
|
second parameter to `true` causes it to return all success parameters |
|
as an array: |
|
|
|
```javascript |
|
let request = denodeify(require('request'), true); |
|
|
|
request('http://example.com').then(function(result) { |
|
// result[0] -> res |
|
// result[1] -> body |
|
}); |
|
``` |
|
|
|
Or if you pass it an array with names it returns the parameters as a hash: |
|
|
|
```javascript |
|
let request = denodeify(require('request'), ['res', 'body']); |
|
|
|
request('http://example.com').then(function(result) { |
|
// result.res |
|
// result.body |
|
}); |
|
``` |
|
|
|
Sometimes you need to retain the `this`: |
|
|
|
```javascript |
|
let app = require('express')(); |
|
let render = denodeify(app.render.bind(app)); |
|
``` |
|
|
|
The denodified function inherits from the original function. It works in all |
|
environments, except IE 10 and below. Consequently all properties of the original |
|
function are available to you. However, any properties you change on the |
|
denodeified function won't be changed on the original function. Example: |
|
|
|
```javascript |
|
let request = denodeify(require('request')), |
|
cookieJar = request.jar(); // <- Inheritance is used here |
|
|
|
request('http://example.com', {jar: cookieJar}).then(function(res) { |
|
// cookieJar.cookies holds now the cookies returned by example.com |
|
}); |
|
``` |
|
|
|
Using `denodeify` makes it easier to compose asynchronous operations instead |
|
of using callbacks. For example, instead of: |
|
|
|
```javascript |
|
let fs = require('fs'); |
|
|
|
fs.readFile('myfile.txt', function(err, data){ |
|
if (err) { ... } // Handle error |
|
fs.writeFile('myfile2.txt', data, function(err){ |
|
if (err) { ... } // Handle error |
|
console.log('done') |
|
}); |
|
}); |
|
``` |
|
|
|
you can chain the operations together using `then` from the returned promise: |
|
|
|
```javascript |
|
let fs = require('fs'); |
|
let readFile = denodeify(fs.readFile); |
|
let writeFile = denodeify(fs.writeFile); |
|
|
|
readFile('myfile.txt').then(function(data){ |
|
return writeFile('myfile2.txt', data); |
|
}).then(function(){ |
|
console.log('done') |
|
}).catch(function(error){ |
|
// Handle error |
|
}); |
|
``` |
|
|
|
@method denodeify |
|
@public |
|
@static |
|
@for rsvp |
|
@param {Function} nodeFunc a 'node-style' function that takes a callback as |
|
its last argument. The callback expects an error to be passed as its first |
|
argument (if an error occurred, otherwise null), and the value from the |
|
operation as its second argument ('function(err, value){ }'). |
|
@param {Boolean|Array} [options] An optional paramter that if set |
|
to `true` causes the promise to fulfill with the callback's success arguments |
|
as an array. This is useful if the node function has multiple success |
|
paramters. If you set this paramter to an array with names, the promise will |
|
fulfill with a hash with these names as keys and the success parameters as |
|
values. |
|
@return {Function} a function that wraps `nodeFunc` to return a `Promise` |
|
*/ |
|
export default function denodeify(nodeFunc, options) { |
|
let fn = function() { |
|
let l = arguments.length; |
|
let args = new Array(l + 1); |
|
let promiseInput = false; |
|
|
|
for (let i = 0; i < l; ++i) { |
|
let arg = arguments[i]; |
|
let then; |
|
|
|
// TODO: this code really needs to be cleaned up |
|
if (!promiseInput) { |
|
if (arg !== null && typeof arg === 'object') { |
|
if (arg.constructor === Promise) { |
|
promiseInput = true; |
|
} else { |
|
try { |
|
promiseInput = arg.then; |
|
} catch(error) { |
|
let p = new Promise(noop); |
|
reject(p, error); |
|
return p; |
|
} |
|
} |
|
} else { |
|
promiseInput = false; |
|
} |
|
if (promiseInput && promiseInput !== true) { |
|
arg = wrapThenable(promiseInput, arg); |
|
} |
|
} |
|
args[i] = arg; |
|
} |
|
|
|
let promise = new Promise(noop); |
|
|
|
args[l] = function(err, val) { |
|
if (err) { |
|
reject(promise, err); |
|
} else if (options === undefined) { |
|
resolve(promise, val); |
|
} else if (options === true) { |
|
resolve(promise, arrayResult(arguments)); |
|
} else if (Array.isArray(options)) { |
|
resolve(promise, makeObject(arguments, options)); |
|
} else { |
|
resolve(promise, val); |
|
} |
|
}; |
|
|
|
if (promiseInput) { |
|
return handlePromiseInput(promise, args, nodeFunc, this); |
|
} else { |
|
return handleValueInput(promise, args, nodeFunc, this); |
|
} |
|
}; |
|
|
|
fn.__proto__ = nodeFunc; |
|
|
|
return fn; |
|
} |
|
|
|
function handleValueInput(promise, args, nodeFunc, self) { |
|
try { |
|
nodeFunc.apply(self, args); |
|
} catch (error) { |
|
reject(promise, error); |
|
} |
|
return promise; |
|
} |
|
|
|
function handlePromiseInput(promise, args, nodeFunc, self){ |
|
return Promise.all(args) |
|
.then(args => handleValueInput(promise, args, nodeFunc, self)); |
|
}
|
|
|