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.
(TOC generated by verb using markdown-toc)
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
anduncaughtException
- 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 latestv6+
Node.js - accept and works with javascript internal functions like
JSON.stringify
andJSON.parse
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.
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)
// => errors only happened in function wrapped by relike
})
foo
// => throws ReferenceError directly, immediately
// and your application will crash
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)
// => ReferenceError: foo is not defined
})
foo
// => never throws directly, never crash
// this error should be handled from the promise
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.
npm i letta --save
For more use-cases see the tests, examples or all 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) // => 123
}, 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) // => 456
}, 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) // => 'letta'
})
.catch(SyntaxError, err => {
console.error('File had syntax error', err)
})
// Catch any other error
.catch(err => {
console.error(err.stack)
})
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 tofn
.returns
{Promise}: Always native Promise if supported on enviroment.
Example
const letta = require('letta')
letta((foo, bar, baz) => {
console.log(foo, bar, baz) // => 'foo bar baz'
return foo
}, 'foo', 'bar', 'baz')
.then(console.log) // => 'foo'
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 givenfn
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) // => 'letta'
})
.catch(SyntaxError, err => {
console.error('File had syntax error', err)
})
// Catch any other error
.catch(err => {
console.error(err.stack)
})
// or promisify generator function
const promise = letta(function * () {
let result = yield Promise.resolve(123)
return result
})
promise.then(value => {
console.log(value) // => 123
}, err => {
console.error(err.stack)
})
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') // using `Q` promise on node 0.10
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') // using `when` promise on node 0.10
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) // => 'letta'
}, console.error)
console.log(promise.Promise) // => used Promise constructor
console.log(promise.___bluebirdPromise) // => `true` on old env, falsey otherwise
console.log(promise.___customPromise) // => `true` when pass `.Promise`, falsey otherwise
Few working examples with what can be passed and how
letta
acts.
- Callback functions
- Generator functions
- JSON.stringify
- Synchronous functions
- Exceptions and rejections
- Returning errors
- Passing function as last argument
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) // => 'letta'
}, err => {
console.error(err.stack)
})
// callback `fs.stat` function
letta(fs.stat, 'package.json')
.then(stats => {
console.log(stats.isFile()) // => true
}, err => {
console.error(err.stack)
})
Accept generator functions same as
co
and acts likeco@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) // => 'letta'
}, err => {
console.error(err.stack)
})
Specific use-case which shows correct handling of optional arguments.
const letta = require('letta')
letta(JSON.stringify, { foo: 'bar' })
.then(data => {
console.log(data) // => {"foo":"bar"}
}, console.error)
// result with identation
letta(JSON.stringify, {foo: 'bar'}, null, 2)
.then(data => {
console.log(data)
// =>
// {
// "foo": "bar"
// }
}, console.error)
Again, showing correct handling of optinal arguments using native
fs
module.
const fs = require('fs')
const letta = require('letta')
// sync function
letta(fs.statSync, 'package.json')
.then(stats => {
console.log(stats.isFile()) // => true
})
.catch(err => console.error(err.stack))
// correct handling of optional arguments
letta(fs.readFileSync, 'package.json')
.then(buf => {
console.log(Buffer.isBuffer(buf)) // => true
})
.catch(err => {
console.error(err.stack)
})
Handles
uncaughtException
andunhandledRejection
by default.
Example
const fs = require('fs')
const letta = require('letta')
letta(fs.readFile, 'foobar.json')
.then(console.log, err => {
console.error(err.code) // => 'ENOENT'
})
// handles ReferenceError,
// SyntaxError and etc
const promise = letta(function () {
foo
return true
})
promise.catch(err => {
console.error(err) // => 'ReferenceError: foo is not defined'
})
You should notice that if some function returns instance of
Error
it will acts as usual - receive it in.then
not in.catch
. Review theexamples/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) // => true
console.log(errorAsResultValue.message) // => 'foo err bar'
})
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) // => { a: 'b' }
})
- callback2stream: Transform sync, async or generator function to Stream. Correctly handle errors. homepage
- letta-value: Extends
letta
to accept and handles more than functions only. Handles all… more | homepage - mukla: Simple and fast test runner with basic reporter and clean stacktraces. Support… more | homepage
- promise2stream: Transform ES2015 Promise to Stream - specifically, Transform Stream using… more | homepage
- relike-all: Promisify all functions in an object, using relike. | homepage
- relike-value: Create promise from sync, async, string, number, array and so on. Handle… more | homepage
- relike: Simple promisify async or sync function with sane defaults. Lower level than… more | homepage
- value2stream: Transform any value to stream. Create a stream from any value -… more | homepage
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.