index.js
/*!
* redolent <https://github.com/tunnckoCore/redolent>
*
* Copyright (c) Charlike Mike Reagent <@tunnckoCore> (https://i.am.charlike.online)
* Released under the MIT license.
*/
'use strict'
import arrify from 'arrify'
import sliced from 'sliced'
import extend from 'extend-shallow'
import register from 'native-or-another/register'
import Promize from 'native-or-another'
import isAsync from 'is-async-function'
/**
* > Will try to promisify `fn` with native Promise,
* otherwise you can give different promise module
* to `opts.Promise`, for example [pinkie][] or [bluebird][].
* If `fn` [is-async-function][] it will be passed with `done` callback
* as last argument - always concatenated with the other provided args
* through `opts.args`.
*
* **Note:** Uses [native-or-another][] for detection, so it will always will use the
* native Promise, otherwise will try to load some of the common promise libraries
* and as last resort if can't find one of them installed, then throws an Error!
*
* **Example**
*
* ```js
* const fs = require('fs')
* const request = require('request')
* const redolent = require('redolent')
*
* redolent(fs.readFile)('package.json', 'utf-8').then(data => {
* console.log(JSON.parse(data).name)
* })
*
* // handles multiple arguments by default
* redolent(request)('http://www.tunnckocore.tk/').then(result => {
* const [httpResponse, body] = result
* })
*
* // `a` and `b` arguments comes from `opts.args`
* // `c` and `d` comes from the call of the promisified function
* const fn = redolent((a, b, c, d, done) => {
* console.log(typeof done) // => 'function'
* done(null, a + b + c + d)
* }, {
* args: [1, 2]
* })
*
* fn(3, 5).then((res) => {
* console.log(res) // => 11
* })
* ```
*
* @name redolent
* @param {Function} `<fn>` a function to be promisified
* @param {Object} `[opts]` optional options, also passed to [native-or-another][]
* @param {Array} `[opts.args]` additional arguments to be passed to `fn`,
* all args from `opts.args` and these that are
* passed to promisifed function are concatenated
* @param {Object} `[opts.context]` what context to be applied to `fn`,
* by default it is smart enough and applies
* the `this` context of redolent call or the call
* of the promisified function
* @param {Function} `[opts.Promise]` custom Promise constructor for versions `< v0.12`,
* like [bluebird][] for example, by default
* it **always** uses the native Promise in newer
* node versions
* @param {Boolean} `[opts.global]` defaults to `true`, pass false if you don't
* want to attach/add/register the given promise
* to the `global` scope, when node `< v0.12`
* @return {Function} promisified function
* @throws {TypeError} If `fn` is not a function
* @throws {TypeError} If no promise is found
* @api public
*/
export default function redolent (fn, opts) {
if (typeof fn !== 'function') {
throw new TypeError('redolent: expect `fn` to be a function')
}
opts = extend({ context: this, Promise: Promize }, opts)
opts.Promise = register(opts)
// we can't test that here, because some
// of our devDeps has some Promise library,
// so it's loaded by `native-or-another` automatically
/* istanbul ignore next */
if (typeof opts.Promise !== 'function') {
var msg = 'no native Promise support nor other promise were found'
throw new TypeError('redolent: ' + msg)
}
return function () {
opts.context = this || opts.context
opts.args = arrify(opts.args).concat(sliced(arguments))
var promise = new opts.Promise(function (resolve, reject) {
var called = false
function done (er, res) {
called = true
if (er) {
return reject(er)
}
if (arguments.length > 2) {
res = sliced(arguments, 1)
}
return resolve(res)
}
var isAsyncFn = isAsync(fn)
opts.args = isAsyncFn ? opts.args.concat(done) : opts.args
var syncResult = fn.apply(opts.context, opts.args)
var xPromise = isPromise(syncResult, opts.Promise)
var hasPromiseReturn = isAsyncFn && !called && xPromise
if ((!isAsyncFn && !called) || hasPromiseReturn) {
resolve(syncResult)
return
}
if (isAsyncFn && !xPromise && syncResult !== undefined) {
var msg = 'Asynchronous functions can only return a Promise or invoke a callback'
reject(new Error('redolent: ' + msg))
}
})
return normalize(promise, opts.Promise)
}
}
function normalize (promise, Ctor) {
promise.___nativePromise = Boolean(Ctor.___nativePromise)
promise.___customPromise = Boolean(Ctor.___customPromise)
return promise
}
/* istanbul ignore next */
function isPromise (val, Promize) {
return val instanceof Promize || (
val !== null &&
typeof val === 'object' &&
typeof val.then === 'function' &&
typeof val.catch === 'function'
)
}