diff --git a/src/log.js b/src/log.js index 68daacddca2d..9d421dc45343 100644 --- a/src/log.js +++ b/src/log.js @@ -186,24 +186,8 @@ export class Log { if (this.level_ >= LogLevel.ERROR) { this.msg_(tag, 'ERROR', Array.prototype.slice.call(arguments, 1)); } else { - let error = null; - let message = ''; - for (let i = 1; i < arguments.length; i++) { - const arg = arguments[i]; - if (arg instanceof Error && !error) { - error = arg; - } else { - if (message) { - message += ' '; - } - message += arg; - } - } - if (!error) { - error = new Error(message); - } else if (message) { - error.message = message + ': ' + error.message; - } + const error = createErrorVargs.apply(null, + Array.prototype.slice.call(arguments, 1)); this.prepareError_(error); this.win.setTimeout(() => {throw error;}); } @@ -211,12 +195,11 @@ export class Log { /** * Creates an error object. - * @param {string|!Error} errorOrMessage + * @param {...*} var_args * @return {!Error} */ - createError(errorOrMessage) { - const error = typeof errorOrMessage == 'string' ? - new Error(errorOrMessage) : errorOrMessage; + createError(var_args) { + const error = createErrorVargs.apply(null, arguments); this.prepareError_(error); return error; } @@ -331,6 +314,45 @@ function pushIfNonEmpty(array, val) { } +/** + * @param {...*} var_args + * @return {!Error} + * @private + */ +function createErrorVargs(var_args) { + let error = null; + let message = ''; + for (let i = 0; i < arguments.length; i++) { + const arg = arguments[i]; + if (arg instanceof Error && !error) { + error = arg; + } else { + if (message) { + message += ' '; + } + message += arg; + } + } + if (!error) { + error = new Error(message); + } else if (message) { + error.message = message + ': ' + error.message; + } + return error; +} + + +/** + * Rethrows the error without terminating the current context. This preserves + * whether the original error designation is a user error or a dev error. + * @param {...*} var_args + */ +export function rethrowAsync(var_args) { + const error = createErrorVargs.apply(null, arguments); + setTimeout(() => {throw error;}); +} + + /** * @deprecated Use either publog or devlog * TODO(dvoytenko, #2527): Remove this constant. diff --git a/test/functional/test-log.js b/test/functional/test-log.js index 6c4a9a575c01..125951ff36bf 100644 --- a/test/functional/test-log.js +++ b/test/functional/test-log.js @@ -20,6 +20,7 @@ import { USER_ERROR_SENTINEL, dev, isUserErrorMessage, + rethrowAsync, user, } from '../../src/log'; import {setModeForTesting} from '../../src/mode'; @@ -445,4 +446,71 @@ describe('Logging', () => { .to.throw('Unknown enum value: "VALUE1"'); }); }); + + + describe('rethrowAsync', () => { + let clock; + + beforeEach(() => { + clock = sandbox.useFakeTimers(); + }); + + it('should rethrow error with single message', () => { + rethrowAsync('intended'); + expect(() => { + clock.tick(1); + }).to.throw(Error, /^intended$/); + }); + + it('should rethrow a single error', () => { + const orig = new Error('intended'); + rethrowAsync(orig); + let error; + try { + clock.tick(1); + } catch (e) { + error = e; + } + expect(error).to.equal(orig); + expect(error.message).to.equal('intended'); + }); + + it('should rethrow error with many messages', () => { + rethrowAsync('first', 'second', 'third'); + let error; + try { + clock.tick(1); + } catch (e) { + error = e; + } + expect(error.message).to.equal('first second third'); + }); + + it('should rethrow error with original error and messages', () => { + const orig = new Error('intended'); + rethrowAsync('first', orig, 'second', 'third'); + let error; + try { + clock.tick(1); + } catch (e) { + error = e; + } + expect(error).to.equal(orig); + expect(error.message).to.equal('first second third: intended'); + }); + + it('should preserve error suffix', () => { + const orig = user.createError('intended'); + expect(isUserErrorMessage(orig.message)).to.be.true; + rethrowAsync('first', orig, 'second'); + let error; + try { + clock.tick(1); + } catch (e) { + error = e; + } + expect(error).to.equal(orig); + expect(isUserErrorMessage(error.message)).to.be.true; + }); + }); });