Skip to content

Instantly share code, notes, and snippets.

@pmuellr
Last active February 26, 2016 13:24
Show Gist options
  • Save pmuellr/a53baaa0b218de152eda to your computer and use it in GitHub Desktop.
Save pmuellr/a53baaa0b218de152eda to your computer and use it in GitHub Desktop.
Call functions that take async callbacks, in a synchronous style, in a generator, using yield.
'use strict'
module.exports = yieldCallback
// Call functions that use async callbacks, in an "synchronous" type style from
// within a generator.
if (require.main === module) test()
// example invoking a yielding callback generator
function test() {
yieldCallback(testGen, [__filename], function testCB ($) {
if ($.err) return console.log(`error reading ${__filename}: ${$.err}`)
console.log(`${__filename} length: ${$.content.length}`)
})
}
// our yielding callback generator
function * testGen (fileName, cbProps) {
const fs = require('fs')
let $
yield wait(cbProps())
console.log('opening file')
$ = yield fs.open(fileName, 'r', cbProps('err fd'))
if ($.err) return $
const fd = $.fd
yield wait(cbProps())
console.log('getting file size')
$ = yield fs.fstat(fd, cbProps('err stats'))
if ($.err) { yield fs.close(fd, cbProps()); return $ }
if (!$.stats.isFile()) return { err: new Error('not a file') }
const buff = new Buffer($.stats.size)
yield wait(cbProps())
console.log('reading file')
$ = yield fs.read(fd, buff, 0, buff.length, 0, cbProps('err bytesRead buffer'))
if ($.err) { yield fs.close(fd, cbProps()); return $ }
if ($.bytesRead !== buff.length) return { err: new Error('unexpected file size') }
const content = $.buffer.slice(0, $.bytesRead).toString('utf8')
yield wait(cbProps())
console.log('closing file')
$ = yield fs.close(fd, cbProps('err'))
if ($.err) return $
yield wait(cbProps())
console.log('returning file contents')
return { content: content }
}
function wait (cb) {
console.log('waiting a half second')
setTimeout(cb, 500)
}
// call with (generatorFn, [arg1, arg2], cb)
function yieldCallback (gen, genArgs, cb) {
// add cbProps to the argument list
genArgs.push(cbProps)
// call the generator
const iter = gen.apply(null, genArgs)
// proceed to first yield
iter.next()
// code in generatorFn calls this with names of callback arguments
function cbProps (vars) {
if (!vars) vars = ''
// split into individual "variable" names
vars = vars.trim().split(/\s+/)
// this function will be the fn passed as a callback
return function cbParmSetter () {
// create the object returned from the yield
const object = {}
// set the callback arguments as properties of the object
for (let i = 0; i < vars.length; i++) {
object[vars[i]] = arguments[i]
}
// return the object from yield, continue to next yield
const next = iter.next(object)
// if we're done, call the callback with the value returned
if (next.done) cb(next.value)
}
}
}
@pmuellr
Copy link
Author

pmuellr commented Feb 26, 2016

yieldCallback(generator, generatorArguments, cb)

Call the specified generator function, with the specified arguments. When the generator returns, cb() will be invoked with the result.

The generator function is called with the arguments you supply, and an additional one at the end, cbProps - a function.

In place of the usual callback parameter that you'd use with an function that takes an async callback function, use cbProps('err data'), which will cause the function invocation via yield to return an object {err: something, data: something}. The argument to cbProps() is a space-separated list of property names to set in the returned object. Those properties will be filled in from whatever the real callback is called with.

Whatever the generator returns, will be passed to the callback passed to the yieldCallback() function.

In this example, I use $ as my running result from fs functions. As needed, copy those into their own variables, eg, fd.

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