
Write super-clean async code with promises.
Promises work great to reduce "right-ward drift" as seen with code using regular node-style callbacks. It also eases error handling, relieving you from the necessity to check for errors after each asynchronous step.
However, I found code that goes further than merely processing return values in a chain (comparable to chaining synchronous function calls) still too noisy, and writing too cumbersome.
With Stepthrough, you can use fulfilled promises in later steps without having to call then, and with access to more than one "return value" without explicitly specifying variables in an outer scope up-front.
When using CoffeeScript, the resulting code looks very close to regular synchronous code blocks.
In this example, we attempt to build some kind of email message piece by piece before sending it.
stepthrough = require "stepthrough"
sendMail = ->
stepthrough [
-> @name = "Meryn Stol" # real
-> @email = "merynstol@gmail.com" # real
-> @subject = consoleAPI.askForLine "Message subject" # promise
-> @body = consoleAPI.askForText "Message body" # promise
-> @location = locationAPI.guessFriendlyName() # promise
-> @blurp = "\n\nWritten in #{@location}" # real
-> @body = @body + @blurp
-> @signature = signatureAPI.signMessage @name, @email, @subject, @body
-> @mailResult = mailAPI.send @name, @email, @subject, @body + @signature
-> console.log "Successfully sent your message '#{@subject}' at #{@mailResult.getFriendlyTime()}."
]
In short, stepthrough steps through an array of step functions. It returns a promise which is fulfilled when all steps are completed.
A step function can get and set properties on a provided memo object. This memo object essentially takes over the role of what normally be the local function scope in typical synchronous code.
After a step function has returned, Stepthrough will do the following:
What happens next depends on whether the promises are fulfilled:
stepthrough is fulfilled.stepthrough is then rejected, unless a special error handler is specified.then) to a variable in the outer function context. This will save you one line of code for any value of promise you need to have available further down in the chain.stepthrough(steps)
You simply call stepthrough with an array containing any number of functions. These functions are called in turn with the value of memo as both the function context (this) and the first argument for the function.
stepthrough(initialMemo, steps)
Optionally, you may provide a memo object as first argument. This object will be used as the initial value for the memo object. The properties of this object will be modified. The object reference stays the same. Calling stepthrough(steps) is the same as calling stepthrough({}, steps)
errorHandler = function(err, memo) {
console.log(this) // prints final value of the memo object
console.log(memo) // idem
throw err
}
stepthrough(memo, steps, errorCallback)
Additionally, you may provide an error handler function as last argument. This error handler has access to the final version of the memo object. This is useful when you want to do any cleanup, for example closing file-descriptors or rolling back a transaction. Essentially, this function as the catch clause of a try...catch block.
If the error handler does not throw an error, the promise returned by stepthrough will be fulfilled anyway.
The initial structure of this module was generated by Jumpstart, using the Jumpstart Black Coffee template.
stepthrough is released under the MIT License.
Copyright (c) 2013 Meryn Stol