-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.js
153 lines (137 loc) · 5.15 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
/*!
* 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'
)
}