Skip to content

Instantly share code, notes, and snippets.

@jbenet

jbenet/ctx.js Secret

Created August 30, 2016 01:40
Show Gist options
  • Save jbenet/11671be17cbe91c67a00d9638d99a45d to your computer and use it in GitHub Desktop.
Save jbenet/11671be17cbe91c67a00d9638d99a45d to your computer and use it in GitHub Desktop.
// this is a very rough draft idea of porting
// https://golang.org/pkg/context/ to javascript
class Context {
constructor() {
// _cancelled determines whether this context has
// been cancelled already
this._cancelled = false,
// _cancelconds is a set of cancel conditions, which
// when true will automatically cancel the context.
// these are checked every time the .done() function
// is called, so be careful with putting anything
// intensive there. (TODO: maybe this is a bad idea)
this._cancelConds = []
// final error
this._err = null
}
// on attaches callback cb to happen when cancelled
onCancel(cb) {
this._on('cancel', cb)
}
addCancelCondition(fn) {
if (typeof(fn) !== 'function') {
return
}
this._cancelConds.push(fn)
}
// cancel cancels this context.
cancel(err) {
err = err || "cancel function called"
this._err = err
this._cancelled = true
this._trigger('cancel', this)
}
// done returns whether the context is "done"
done() {
if (this._cancelled) {
return true
}
// this may be super expensive. maybe dont do it
for (var i in this._cancelConds) {
if (this._cancelConds[i]()) {
this.cancel("cancel condition met")
return true
}
}
}
// err returns an error, if any. calling err
// before done() is true is undefined.
err() {
return this._err
}
}
var WithParent = (parentCtx) => {
var child = new Context()
parentCtx.on('cancel', child.cancel)
return child
}
var WithTimeout = (parentCtx, millis) => {
millis = (millis < 0 ? 0 : millis)
var ctx = WithParent(parentCtx)
setInterval(ctx.cancel, millis)
return ctx
}
var WithDeadline = (parentCtx, deadline) => {
var time = deadline - timeNow()
var ctx = WithTimeout(parentCtx, time)
ctx.addCancelCondition(() => {
return Date.now() > deadline
})
// just in case the deadline is already passed.
process.nextTick(ctx.done)
return ctx
}
// calling code
var ctx = WithParent(Background)
doSomethingExpensive(ctx, cb)
ctx.cancel()
func doSomethingExpensive(ctx, cb) {
if (ctx.done()) {
return cb(ctx.err())
}
doAnotherExpensiveThing((err, val) => {
if (err) {
return cb(err)
}
if (ctx.done()) {
return cb(ctx.err())
}
doAnotherExpensiveThing((err, val) => {
if (err) {
return cb(err)
}
if (ctx.done()) {
return cb(ctx.err())
}
cb(null, val)
})
})
}
doCancellableXHR(ctx, cb)
ctx.cancel() // cancels the something
func doCancellableXHR(ctx, cb) {
var done = false
var xhr = sendXHR(...)
xhr.on('error', cb)
xhr.on('success', () => {
done = true
})
ctx.onCancel((err) => {
if (done || xhr.isDone()) return
xhr.abort()
cb(err)
})
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment