Skip to content

Commit

Permalink
fix(ErrorObservable): will now propagate errors properly when used in…
Browse files Browse the repository at this point in the history
… a `catch` after `fromPromise`. (#2552)

* test(Observable.catch): Added failing test when errors bubble up from a PromiseObservable.

* fix(ErrorObservable): allow Observable.throw to rethrow uncaught errors;
  • Loading branch information
trshafer authored and benlesh committed May 17, 2017
1 parent df78c4c commit cf88a20
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 1 deletion.
80 changes: 80 additions & 0 deletions spec/operators/catch-spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {expect} from 'chai';
import * as Rx from '../../dist/cjs/Rx';
import * as sinon from 'sinon';
import {createObservableInputs} from '../helpers/test-helper';
import marbleTestingSignature = require('../helpers/marble-testing'); // tslint:disable-line:no-require-imports

Expand Down Expand Up @@ -278,4 +279,83 @@ describe('Observable.prototype.catch', () => {
done();
});
});

context('fromPromise', () => {
type SetTimeout = (callback: (...args: any[]) => void, ms: number, ...args: any[]) => NodeJS.Timer;

let trueSetTimeout: SetTimeout;
let sandbox: sinon.SinonSandbox;
let timers: sinon.SinonFakeTimers;

beforeEach(() => {
trueSetTimeout = global.setTimeout;
sandbox = sinon.sandbox.create();
timers = sandbox.useFakeTimers();
});

afterEach(() => {
sandbox.restore();
});

it('should chain a throw from a promise using throw', (done: MochaDone) => {
const subscribeSpy = sinon.spy();
const testError = new Error('BROKEN PROMISE');
Observable.fromPromise(Promise.reject(testError)).catch(err => {
throw new Error('BROKEN THROW');
}).subscribe(subscribeSpy);

trueSetTimeout(() => {
try {
timers.tick(1);
} catch (e) {
expect(subscribeSpy).not.to.be.called;
expect(e.message).to.equal('BROKEN THROW');
return done();
}
done(new Error('This should have thrown an error'));
}, 0);
});

it('should chain a throw from a promise using Observable.throw', (done: MochaDone) => {
const subscribeSpy = sinon.spy();
const testError = new Error('BROKEN PROMISE');
Observable.fromPromise(Promise.reject(testError)).catch(err =>
Observable.throw(new Error('BROKEN THROW'))
).subscribe(subscribeSpy);

trueSetTimeout(() => {
try {
timers.tick(1);
} catch (e) {
expect(subscribeSpy).not.to.be.called;
expect(e.message).to.equal('BROKEN THROW');
return done();
}
done(new Error('This should have thrown an error'));
}, 0);
});

it('should chain a throw from a promise using Observable.throw', (done: MochaDone) => {
const subscribeSpy = sinon.spy();
const errorSpy = sinon.spy();
const thrownError = new Error('BROKEN THROW');
const testError = new Error('BROKEN PROMISE');
Observable.fromPromise(Promise.reject(testError)).catch(err =>
Observable.throw(thrownError)
).subscribe(subscribeSpy, errorSpy);

trueSetTimeout(() => {
try {
timers.tick(1);
} catch (e) {
return done(new Error('This should not have thrown an error'));
}
expect(subscribeSpy).not.to.be.called;
expect(errorSpy).to.have.been.called;
expect(errorSpy).to.have.been.calledWith(thrownError);
done();
}, 0);
});
});

});
5 changes: 4 additions & 1 deletion src/observable/ErrorObservable.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { IScheduler } from '../Scheduler';
import { Observable } from '../Observable';
import { TeardownLogic } from '../Subscription';
import { Subscriber } from '../Subscriber';

export interface DispatchArg {
error: any;
Expand Down Expand Up @@ -67,10 +68,12 @@ export class ErrorObservable extends Observable<any> {
super();
}

protected _subscribe(subscriber: any): TeardownLogic {
protected _subscribe(subscriber: Subscriber<any>): TeardownLogic {
const error = this.error;
const scheduler = this.scheduler;

subscriber.syncErrorThrowable = true;

if (scheduler) {
return scheduler.schedule(ErrorObservable.dispatch, 0, {
error, subscriber
Expand Down

0 comments on commit cf88a20

Please sign in to comment.