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

example

// will be resolved
new Promise(function ($res, $rej, ifCanceled) {
  var internal = setTimeout($rej, 1000);
  ifCanceled(function () {
    clearTimeout(internal);
  });
})
// will be resolved without executing
.then(
  function () {
    console.log('on time');
  },
  function () {
    console.log('error');
  }
)
.cancel({beacuse:'reason'})
// will simply execute as resolved
.then(function (value) {
  console.log(value);
});

@mariusGundersen
Copy link

What is the usecase for resolving a canceled promise to undefined? Wouldn't that mean that all successive then's in the chain have to check if they received undefined? Isn't that kind of checking what promises try to avoid by having a separate handler for errors?

@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