Skip to content

Instantly share code, notes, and snippets.

@dead-claudia
Last active December 11, 2015 11:58
Show Gist options
  • Save dead-claudia/0ea14936a1680065a3a3 to your computer and use it in GitHub Desktop.
Save dead-claudia/0ea14936a1680065a3a3 to your computer and use it in GitHub Desktop.
Monadified error-first callbacks in LiveScript, inspired by JS's async-await proposal. Version in JS is for speed.
/** See LICENSE.md for the license for this code. */
// A version written in ES6, sans modules.
/**
* It does return the return value of the function, but forces async calling of
* the callback and async handling of errors after the first async call.
*
* f = async (await, ...args, next) !->
* if args.length == 1 => throw new Error 'bad!' # throw sync error
* v <-! await fs.readFile \foo, \utf8 # do an async call
* if isBad v => throw new Error 'bad!' # throw async error
* next v # return the value
*
* f (err, data) ->
* process.exit 1 if err?
* console.log data
*
* You can even make methods out of this. You just have to remember
* to bind each backcall.
*
* class Reader
* (@file) ->
* read: async (await, ...args, next) !->
* v <~! await fs.readFile @file, \utf8 # do an async call
* next v # return the value
*/
function once(f) {
return function () {
if (f !== undefined) {
var g = f
f = null
return g.apply(this, arguments)
}
}
}
function attempt(err, f) {
try {
return f()
} catch (e) {
return err(e)
}
}
// The main combinator
module.exports = function async(f) {
function g(...args) {
function await(f, ...args) {
// shadowed variables not used
const next = once(args.pop())
return attempt(ret, () => f(...args, (err, ...rest) => {
if (err != null) return ret(err)
return setImmediate(() => next(...rest))
}))
}
const ret = once(args.pop())
return f.call(this, ...args, (...rest) => ret(null, ...rest))
}
const desc = Object.getOwnPropertyDescriptor(f, "length")
desc.value--
return Object.defineProperty(g, "length", desc)
}
/** See LICENSE.md for the license for this code. */
/**
* It does return the return value of the function, but forces async calling of
* the callback and async handling of errors after the first async call.
*
* f = async (await, ...args, next) !->
* if args.length == 1 => throw new Error 'bad!' # throw sync error
* v <-! await fs.readFile \foo, \utf8 # do an async call
* if isBad v => throw new Error 'bad!' # throw async error
* next v # return the value
*
* f (err, data) ->
* process.exit 1 if err?
* console.log data
*
* You can even make methods out of this. You just have to remember
* to bind each backcall.
*
* class Reader
* (@file) ->
* read: async (await, ...args, next) !->
* v <~! await fs.readFile @file, \utf8 # do an async call
* next v # return the value
*/
function once(f) {
return function () {
if (f !== undefined) {
var g = f
f = null
return g.apply(this, arguments)
}
}
}
function attempt(err, f) {
try {
return f()
} catch (e) {
return err(e)
}
}
// The main combinator
module.exports = function async(f) {
function g() {
var args = [function (f) {
// shadowed variables not used
var args = []
for (var i = 1, len = arguments.length - 1; i < len; i++) {
args.push(arguments[i])
}
var next = once(arguments[len])
args.push(function (err) {
if (err != null) return ret(err)
var rest = []
for (var i = 1; i < arguments.length; i++) {
rest.push(arguments[i])
}
return setImmediate(function () {
return next.apply(null, rest)
})
})
return attempt(ret, function () {
return f.apply(null, args)
})
}]
for (var i = 0, len = arguments.length - 1; i < len; i++) {
args.push(arguments[i])
}
var ret = once(arguments[len])
args.push(function () {
var rest = [null]
for (var i = 0, len = arguments.length - 1; i < len; i++) {
rest.push(arguments[i])
}
return ret.apply(null, rest)
})
return f.apply(this, args)
}
var desc = Object.getOwnPropertyDescriptor(f, "length")
desc.value--
return Object.defineProperty(g, "length", desc)
}
/** See LICENSE.md for the license for this code. */
/**
* It does return the return value of the function, but forces async calling of
* the callback and async handling of errors after the first async call.
*
* f = async (await, ...args, next) !->
* if args.length == 1 => throw new Error 'bad!' # throw sync error
* v <-! await fs.readFile \foo, \utf8 # do an async call
* if isBad v => throw new Error 'bad!' # throw async error
* next v # return the value
*
* f (err, data) ->
* process.exit 1 if err?
* console.log data
*
* You can even make methods out of this. You just have to remember
* to bind each backcall.
*
* class Reader
* (@file) ->
* read: async (await, ...args, next) !->
* v <~! await fs.readFile @file, \utf8 # do an async call
* next v # return the value
*/
once = (f) -> ->
if f?
[g, f] = [f, null]
g ... # takes advantage of ES6 tail recursion if it exists
attempt = (err, f) ->
try
f!
catch e
err e
async = (f) ->
g = (...args, ret) ->
await = (f, ...args, next) ->
next = once next
attempt ret, ->
f ...args, (err, ...rest) ->
| err? => ret err
| _ => setImmediate -> next ...rest
f.call @, await, ...args, (...rest) -> ret null, ...rest
desc = Object.getOwnPropertyDescriptor f, \length
desc.value--
Object.defineProperty g, \length, desc

Copyright (c) 2015, Isiah Meadows

Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment