Promisify sync, async or generator function, using relike. Kind of promisify, but lower level. Full compatibility with co4 and passing 100% of its tests.
You might also be interested in relike, relike-all, relike-value and letta-value.
Table Of Contents
(TOC generated by verb using markdown-toc)
Highlights
A few features and main points.
- promisify sync, async and generator functions
- lower lever than "promisify" - giving function to 1st argument, the next arguments are passed to it
- thin wrapper around relike package to add support for generators, using co
- full compatibility with
co@4
and passing 100% of its tests - correct detecting async (callback-style) functions
- correct handling of optional arguments, just
fn.length
not works - believe - great handling of errors, uncaught exceptions, rejections and optional arguments
- never crash, all is silent, listening on
unhandledRejection
and uncaughtException
- always stay to the standards and specs
- always use native Promise, you can't trick that
- only using Bluebird, if not other Promise constructor provided through
.Promise
property - Bluebird or the custom constructor is used only on enviroments that don't have support for native Promise
- works on any nodejs version - from
v0.10.x
to latest v6+
Node.js - accept and works with javascript internal functions like
JSON.stringify
and JSON.parse
Notes
Sync and async functions
Note that it treats functions as asynchronous, based on is-async-function.
Why you should be aware of that? Because if you give async function which don't have last argument called with some of the common-callback-names it will treat that function as synchronous and things may not work as expected.
It's not a problem for most of the cases and for node's native packages, because that's a convention.
Absolutely silent - never crash
Using letta
you should be absolutely careful. Because it makes your application absolutely silent. Which means
if you have some ReferenceError
or something like it, after the execution of letta
it will be muted. And the
only way to handle it is through .catch
from the returned promise.
Let's visualise it. In the following examples we'll use relike first and then letta
, and you can see the differences.
var relike = require('relike')
var promise = relike(function () {
return 123
})
promise.then(console.log, err => {
console.error(err.stack)
})
foo
But the things, using letta
are little bit different, because we have listeners on unhandledRejection
and
on uncaughtException
events. The same example from above, using letta
var letta = require('letta')
var promise = letta(function () {
return 123
})
promise.then(console.log, err => {
console.error(err.stack)
})
foo
So, if you don't want this behavior, you should use relike. But if you want generators support, you should
do some little wrapper for relike.
Install
npm i letta --save
Usage
For more use-cases see the tests, examples or the passing co@4
tests
const fs = require('fs')
const letta = require('letta')
const promise = letta(function * () {
let result = yield Promise.resolve(123)
return result
})
promise.then(value => {
console.log(value)
}, err => {
console.error(err.stack)
})
If you want to convert generator function to regular function that returns a Promise use letta.promisify
Example
const letta = require('letta')
const fn = letta.promisify(function * (number) {
return yield Promise.resolve(number)
})
fn(456).then(number => {
console.log(number)
}, err => {
console.error(err.stack)
})
If you want to promisify any type of function, again, just use the .promisify
method, like you do with bluebird.promisify.
const fs = require('fs')
const letta = require('letta')
const readFile = letta.promisify(fs.readFile)
readFile('package.json', 'utf8')
.then(JSON.parse)
.then(value => {
console.log(value.name)
})
.catch(SyntaxError, err => {
console.error('File had syntax error', err)
})
.catch(err => {
console.error(err.stack)
})
API
Control flow for now and then.
Params
<fn>
{Function}: Regular function (including arrow function) or generator function.[...args]
{Mixed}: Any number of any type of arguments, they are passed to fn
.returns
{Promise}: Always native Promise if supported on enviroment.
Example
const letta = require('letta')
letta((foo, bar, baz) => {
console.log(foo, bar, baz)
return foo
}, 'foo', 'bar', 'baz')
.then(console.log)
Returns a function that will wrap the given fn
. Instead of taking a callback, the returned function will return a promise whose fate is decided by the callback behavior of the given fn
node function. The node function should conform to node.js convention of accepting a callback as last argument and calling that callback with error as the first argument and success value on the second argument. – Bluebird Docs on .promisify
Params
<fn>
{Function}: Regular function (including arrow function) or generator function.[Promize]
{Function}: Promise constructor to be used on enviroment where no support for native.returns
{Function}: Promisified function, which always return a Promise when called.
Example
const fs = require('fs')
const letta = require('letta')
const readFile = letta.promisify(fs.readFile)
readFile('package.json', 'utf8')
.then(JSON.parse)
.then(value => {
console.log(value.name)
})
.catch(SyntaxError, err => {
console.error('File had syntax error', err)
})
.catch(err => {
console.error(err.stack)
})
const promise = letta(function * () {
let result = yield Promise.resolve(123)
return result
})
promise.then(value => {
console.log(value)
}, err => {
console.error(err.stack)
})
.Promise
While letta
always trying to use native Promise if available in the enviroment, you can
give a Promise constructor to be used on enviroment where there's no support - for example, old
broswers or node's 0.10 version. By default, letta
will use and include bluebird on old enviroments,
as it is the fastest implementation of Promises. So, you are able to give Promise constructor, but
it won't be used in modern enviroments - it always will use native Promise, you can't trick that. You
can't give custom promise implementation to be used in any enviroment.
Example
var fs = require('fs')
var letta = require('letta')
letta.Promise = require('q')
var readFile = letta.promisify(fs.readFile)
readFile('package.json', 'utf8')
.then(console.log, err => {
console.error(err.stack)
})
One way to pass a custom Promise constructor is as shown above. But the other way is passing it to .Promise
of the promisified function, like that
var fs = require('fs')
var letta = require('letta')
var statFile = letta.promisify(fs.stat)
statFile.Promise = require('when')
statFile('package.json').then(console.log, console.error)
One more thing, is that you can access the used Promise and can detect what promise is used. It is easy, just as promise.Promise
and you'll get it.
Or look for promise.___bluebirdPromise
and promise.___customPromise
properties. .___bluebirdPromise
(yea, with three underscores in front) will be true if enviroment is old and you didn't provide promise constructor to .Promise
.
So, when you give constructor .__customPromise
will be true and .___bluebirdPromise
will be false.
var fs = require('fs')
var letta = require('letta')
var promise = letta(fs.readFile, 'package.json', 'utf8')
promise.then(JSON.parse).then(function (val) {
console.log(val.name)
}, console.error)
console.log(promise.Promise)
console.log(promise.___bluebirdPromise)
console.log(promise.___customPromise)
Examples
Few working examples with what can be passed and how letta
acts.
Callback functions
Can accept asynchronous (callback) functions as well.
Example
const fs = require('fs')
const letta = require('letta')
letta(fs.readFile, 'package.json', 'utf8')
.then(JSON.parse)
.then(data => {
console.log(data.name)
}, err => {
console.error(err.stack)
})
letta(fs.stat, 'package.json')
.then(stats => {
console.log(stats.isFile())
}, err => {
console.error(err.stack)
})
Generator functions
Accept generator functions same as co
and acts like co@4
.
Example
const fs = require('fs')
const letta = require('letta')
letta(function * (filepath) {
return yield letta(fs.readFile, filepath, 'utf8')
}, 'package.json')
.then(JSON.parse)
.then(data => {
console.log(data.name)
}, err => {
console.error(err.stack)
})
JSON.stringify
Specific use-case which shows correct handling of optional arguments.
const letta = require('letta')
letta(JSON.stringify, { foo: 'bar' })
.then(data => {
console.log(data)
}, console.error)
letta(JSON.stringify, {foo: 'bar'}, null, 2)
.then(data => {
console.log(data)
}, console.error)
Synchronous functions
Again, showing correct handling of optinal arguments using native fs
module.
const fs = require('fs')
const letta = require('letta')
letta(fs.statSync, 'package.json')
.then(stats => {
console.log(stats.isFile())
})
.catch(err => console.error(err.stack))
letta(fs.readFileSync, 'package.json')
.then(buf => {
console.log(Buffer.isBuffer(buf))
})
.catch(err => {
console.error(err.stack)
})
Exceptions and rejections
Handles uncaughtException
and unhandledRejection
by default.
Example
const fs = require('fs')
const letta = require('letta')
letta(fs.readFile, 'foobar.json')
.then(console.log, err => {
console.error(err.code)
})
const promise = letta(function () {
foo
return true
})
promise.catch(err => {
console.error(err)
})
Returning errors
You should notice that if some function returns instance of Error
it will acts as usual - receive it in .then
not in .catch
. Review the examples/errors.js
example.
Example
const letta = require('letta')
const promise = letta(function () {
return new Error('foo err bar')
})
promise.then(errorAsResultValue => {
console.log(errorAsResultValue instanceof Error)
console.log(errorAsResultValue.message)
})
Passing function as last argument
You can also pass normal (non-callback) function as last argument without problem. It won't be assumed as callback, until you name it or have argument with some of common-callback-names.
Example
const assert = require('assert')
const letta = require('letta')
function regular (str, num, obj, fn) {
assert.strictEqual(str, 'foo')
assert.strictEqual(num, 123)
assert.deepEqual(obj, { a: 'b' })
assert.strictEqual(typeof str, 'string')
assert.strictEqual(typeof num, 'number')
assert.strictEqual(typeof obj, 'object')
assert.strictEqual(typeof fn, 'function')
return obj
}
letta(regular, 'foo', 123, {a: 'b'}, function someFn () {})
.then(result => {
console.log(result)
})
Related
Contributing
Pull requests and stars are always welcome. For bugs and feature requests, please create an issue.
But before doing anything, please read the CONTRIBUTING.md guidelines.