Skip to content

Commit

Permalink
Fix issue with Futures created from Promises
Browse files Browse the repository at this point in the history
When a Future created from a Promise would enter crashed state later
along a synchronous pipeline, the original Promise would catch the error
and change its state to rejected. That rejection would never be handled,
and cause the Promise to swallow it, triggering the unhandledRejection
events in some Promise implementations.

Related to #150 and #224.
  • Loading branch information
Avaq committed Jan 21, 2019
1 parent 4838f99 commit b8367d4
Show file tree
Hide file tree
Showing 3 changed files with 45 additions and 6 deletions.
11 changes: 10 additions & 1 deletion src/internal/utils.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import show from 'sanctuary-show';

/* istanbul ignore next: non v8 compatibility */
var setImmediate = typeof setImmediate === 'undefined' ? setImmediateFallback : setImmediate;

export {show};
export function noop(){}
export function moop(){ return this }
Expand Down Expand Up @@ -28,6 +31,12 @@ export function partial3(f, a, b, c){
};
}

export function setImmediateFallback(f, x){
return setTimeout(f, 0, x);
}

export function raise(x){
throw x;
setImmediate(function rethrowErrorDelayedToEscapePromiseCatch(){
throw x;
});
}
13 changes: 13 additions & 0 deletions test/unit/0.utils.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,17 @@ describe('fn', function (){

});

describe('.setImmediateFallback()', function (){

it('calls the function with a value in under 25ms', function (done){
var t = setTimeout(done, 25, new Error('Time is up'));
fn.setImmediateFallback(function (x){
expect(x).to.equal(42);
clearTimeout(t);
done();
}, 42);
});

});

});
27 changes: 22 additions & 5 deletions test/unit/2.methods.mjs
Original file line number Diff line number Diff line change
@@ -1,8 +1,25 @@
import show from 'sanctuary-show';
import {testMethod, futureArg, functionArg} from '../util/props';
import {eq, noop, throws, error} from '../util/util';
import {eq, noop, error} from '../util/util';
import {mock as instance} from '../util/futures';

function raises (done, fn, expected){
if(typeof process.rawListeners !== 'function'){
done();
return;
}

var listeners = process.rawListeners('uncaughtException');
process.removeAllListeners('uncaughtException');
process.once('uncaughtException', function (actual){
listeners.forEach(function (f){ process.on('uncaughtException', f) });
eq(actual, expected);
done();
});

fn();
}

describe('Future prototype', function (){

describe('and()', function (){
Expand Down Expand Up @@ -79,10 +96,10 @@ describe('Future prototype', function (){
mock.fork(a, b);
});

it('throws the interpretation crash value', function (){
it('throws the interpretation crash value', function (done){
var mock = Object.create(instance);
mock._interpret = function (rec){ rec(error) };
throws(function (){ return mock.fork(noop, noop) }, error);
raises(done, function (){ return mock.fork(noop, noop) }, error);
});
});

Expand Down Expand Up @@ -184,10 +201,10 @@ describe('Future prototype', function (){
mock.value(res);
});

it('throws when _interpret calls the rejection callback', function (){
it('throws when _interpret calls the rejection callback', function (done){
var mock = Object.create(instance);
mock._interpret = function (rec, rej){rej(1)};
throws(mock.value.bind(mock, noop), new Error(
raises(done, mock.value.bind(mock, noop), new Error(
'Future#value was called on a rejected Future\n' +
' Rejection: 1\n' +
' Future: ' + show(instance)
Expand Down

0 comments on commit b8367d4

Please sign in to comment.