UNPKG

yoyaku

Version:

Easily create promise-like APIs with this simple wrapper.

256 lines (178 loc) 7.85 kB
Yoyaku ====== * Avoid callback hell with this ultra-simple wrapper for your functions. * Easily streamline node's first-argument-as-error style callbacks. * Automatically wrap entire APIs, such as node's `fs`. * Allows easy currying of functions to an arbitrarily defined depth * Deferred execution: passing a function call without ugly `function(){}` syntax. (See [example](#deferred-execution)) *Serving suggestion*: combine with [async](http://github.com/caolan/async) for added flavour (see [recipe](#async).) ### Example 1 In one line of code, turn node's fs.stat into a promise-like API! Toss `if (err)` to the curb! ```javascript var yoyaku = require("yoyaku"); var exists = yoyaku.yepnope(require("fs").stat); // Now, use it! exists("foo.txt") .nope(function() { console.log("No - we'd better create that file!"); }) .yep(function() { console.log("Yeah my file exists!"); }) ``` ### Example 2 The more flexible method that allows *any* function to be wrapped: ```javascript var yoyaku = require("yoyaku"); function existsInt(file,promises) { fs.stat(file,function(err,data) { if (err) return promises.enoent(); promises.exists(data); }); } var exists = yoyaku(["exists","enoent"],existsInt); exists("./foo.txt") .exists(successFunc) .enoent(failFunc); ``` ### Example 3 Automatically wrapping an entire API for easy promisey access: ```javascript var yoyaku = require("yoyaku"), fs = yoyaku.api(require("fs")); fs.readFile("foo.txt") .yep(function(data) { // do something with the file data }); ``` ### Installing If you're using it in the browser, simply include yoyaku.js in your page. If you're using node, simply `npm install yoyaku`. ### About This won't banish callback hell completely. But used in conjunction with other techniques, it'll certainly help. `Yoyaku` does not mean promise in Japanese - it means 'reservation', 'contract', or 'agreement'. Since this isn't strictly a *promise* API, rather, a *promise-like* API, I decided to use a promise-like word to describe it. Hence, yoyaku. ### Async I find this makes async much more palatable. This is a very small example that I think demonstrates how Yoyaku cleans up the callback syntax. ```javascript var yoyaku = require("yoyaku"), async = yoyaku.api(require("async")), fs = require("fs"), write = yoyaku.yepnope(fs.writeFile), each = async.each; files = [ "foo.txt", "bar.txt", "baz.txt" ]; each(files,fs.readFile) .yep(function(data) { write("combined.txt",data.join("\n")) .yep(somethingToDoWhenFinished); }); function somethingToDoWhenFinished() { console.log("It all worked awesomely!") } ``` ### Deferred execution Deferred execution lets you omit function-expression syntax when passing wrapped functions as callbacks. Instead of calling the function directly, call `.defer` on it and pass in the arguments you normally would. Take this example for instance: ```javascript var yoyaku = require("yoyaku"), mkdirp = yoyaku.yepnope(require("mkdirp")), exists = yoyaku.yepnope(require("fs").stat); function createDirIfMissing(path,callback) { exists(path) .yep(callback) .nope( mkdirp.defer(path) .yep(callback)); } ``` The function simply takes a filepath, and creates the directory specified by the path if it does not already exist. Writing the function without deferred execution and Yoyaku would look like: ```javascript var mkdirp = require("mkdirp"), fs = require("fs"); function createDirIfMissing(path,callback) { fs.stat(path,function(err) { if (!err) return callback(); mkdirp(path,function(err) { if (err) throw err; callback(); }); }); } ``` It's not the reduced line count (5 vs 7 SLOC) that is the main factor in Yoyaku's favour - rather the terseness of the code, and the lack of non-specific cruft, like handlers for error callbacks and function expressions. This has let me be more expressive and feel less hindered by *callback-coding* in my own work. I hope it works for you too. ### Function Reference #### `yoyaku(promiseArray,function)` Wraps a single function in another function that manages promises and callbacks. Passes all the arguments the wrapper function was called with through to the wrapped function, but adds an additional argument on the end: an map of functions named according to the array which was passed to `yoyaku` when it was invoked. #### `yoyaku.yepnope(function, [callwith])` Automatically converts node-style callback APIs (callback as last parameter, error condition passed as first parameter to callback if applicable) to a promise-like API. The returned promises are `.yep` and `.nope`, hence the name of the method. If you do not specify a handler for `.nope`, it will throw an exception should the method fail. This is desirable behaviour (there's nothing worse than invisible errors quietly multiplying inside a program!) #### `yoyaku.api(object)` Runs `yoyaku.yepnope` against every function parameter of an object, and saves the newly wrapped functions on a new object. Essentially this converts an entire API to a promise-like interface. #### `wrappedFunction.defer( ...arguments... )` Defers execution of the function as described in the [deferred execution](#deferred-execution) section of the document above. Takes an arbitrary list of arguments and stores/caches them against a function which it then returns. Executing this returned function will in turn execute the originally wrapped function with these arguments. This function actually calls `wrappedFunction.curry(0, [args])` behind the scenes. #### `wrappedFunction.curry(requiredArgumentCount, [...arguments...] )` Curries the wrapped function. The first parameter tells Yoyaku how many arguments to require before the wrapped function is executed. Any successive parameters are cached, and passed to the final function in order. Until the required number of parameters has been reached or exceeded, Yoyaku will return a function, which has promise setters available as object methods. #### `wrappedFunction.last` (experimental) Contains a reference to the returned promise map generated the last time the wrapped function was called, deferred, or curried. This enables greater brevity, but potentially enables race conditions where the wrapped function might be used simultaneously somewhere else. Use only where you can guarantee linear execution. ```javascript yoyaku.yepnope(fs.stat); fs.stat("myfile"); // do something else fs.stat.last.yep(function() {}); ``` ### Testing npm test ### Licence (BSD) Copyright (c) 2013, Christopher Giffard All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.