Last active
November 8, 2017 12:51
-
-
Save WebReflection/a015c9c02ff2482d327e to your computer and use it in GitHub Desktop.
An attempt to make sense in cancelable Promises world
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(function (Object, Original) {'use strict'; | |
// (C) Andrea Giammarchi - WTFPL | |
if (Original._isPatch) return; | |
var | |
$then = Original.prototype.then, | |
$catch = Original.prototype.catch, | |
defineProperty = Object.defineProperty, | |
defineProperties = Object.defineProperties, | |
commonInterception = function (arr, args, howToCancel) { | |
var o = { | |
self: this, | |
arguments: args | |
}; | |
o.promise = new Promise(function (resolve, reject, ifCanceled) { | |
o.resolve = resolve; | |
o.reject = reject; | |
ifCanceled(howToCancel); | |
}); | |
arr.push(o); | |
return o.promise; | |
}, | |
error = function () { | |
return new Error('Unable to cancel this promise'); | |
} | |
; | |
function Promise(callback) { | |
var | |
resolve, reject, howToCancel, value, | |
state = 'pending', | |
isCancelable = false, | |
shouldCancel = true, | |
p = new Original(function ($resolve, $reject) { | |
resolve = $resolve; | |
reject = $reject; | |
}), | |
cleanWithState = function ($state) { | |
state = $state; | |
// this is not something we care | |
// if access goes slower | |
// so we just drop instance methods | |
// and fallback to those inherited | |
delete p.then; | |
delete p.catch; | |
}, | |
setup = function (value) { | |
var | |
update = function ($value) {value = $value;}, | |
resolved = state === 'resolved', | |
action = resolved ? 'resolve' : 'reject', | |
method = resolved ? 'then' : 'catch' | |
; | |
_then.forEach(function (o) { | |
o[action](value); | |
o.promise | |
.then.apply( | |
o.self, | |
o.arguments | |
) | |
[method](update) | |
; | |
}); | |
_catch.forEach(function (o) { | |
o[action](value); | |
o.promise | |
.catch.apply( | |
o.self, | |
o.arguments | |
) | |
.catch(update) | |
; | |
}); | |
_then = _catch = null; | |
}, | |
_then = [], | |
_catch = [] | |
; | |
function cancel(why) { | |
if (state === 'pending') { | |
cleanWithState('canceled'); | |
if (typeof howToCancel === 'function') { | |
value = why; | |
resolve(why); | |
howToCancel.call(this, why); | |
_then.concat(_catch).forEach(function (o) { | |
o.promise.cancel(why); | |
}); | |
} else { | |
reject(error()); | |
} | |
_then = _catch = null; | |
} | |
return p; | |
} | |
defineProperties(p, { | |
'then': {configurable: true, value: function (f, r) { | |
return isCancelable ? | |
commonInterception.call(this, _then, | |
arguments.length === 2 ? [f, r] : [f], p.cancel) : | |
$then.apply(p, arguments); | |
}}, | |
'catch': {configurable: true, value: function (r) { | |
return isCancelable ? | |
commonInterception.call(this, _catch, [r], p.cancel) : | |
$catch.apply(p, arguments); | |
}} | |
}); | |
callback( | |
function (how) { | |
if (state === 'pending') { | |
cleanWithState('resolved'); | |
resolve.call(this, how); | |
setup.call(this, how); | |
} | |
}, | |
function (why) { | |
if (state === 'pending') { | |
cleanWithState('rejected'); | |
reject.call(this, why); | |
setup.call(this, why); | |
} | |
}, | |
function ifCanceled($howToCancel) { | |
if (typeof howToCancel !== 'function') { | |
if (typeof $howToCancel !== 'function') throw error(); | |
isCancelable = true; | |
howToCancel = function () { | |
if (shouldCancel) { | |
shouldCancel = false; | |
$howToCancel(); | |
} | |
}; | |
defineProperty(p, 'cancel', {value: cancel}); | |
return cancel; | |
} | |
} | |
); | |
return p; | |
} | |
Object.getOwnPropertyNames(Original).forEach(function (name) { | |
if (!(name in Promise)) { | |
defineProperty( | |
Promise, | |
name, | |
Object.getOwnPropertyDescriptor(Original, name) | |
); | |
} | |
}); | |
Promise.prototype = Original.prototype; | |
Promise._isPatch = true; | |
try { | |
module.exports = Promise; | |
} catch(e) { | |
this.Promise = Promise; | |
} | |
}.call(this, Object, Promise)); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
as you can see in the updated example you can now cancel providing a resolved value
https://gist.github.com/WebReflection/a015c9c02ff2482d327e#comment-1422838
meaning all unresolved promises before will be silently resolved but from that point on you have a non-broken behavior.