Skip to content

Instantly share code, notes, and snippets.

@WebReflection
Last active November 8, 2017 12:51
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save WebReflection/a015c9c02ff2482d327e to your computer and use it in GitHub Desktop.
Save WebReflection/a015c9c02ff2482d327e to your computer and use it in GitHub Desktop.
An attempt to make sense in cancelable Promises world
(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));
@WebReflection
Copy link
Author

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.

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