Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Warning: a promise was rejected with a non-error: [object Error] #990

Closed
ColCh opened this issue Feb 3, 2016 · 29 comments
Closed

Warning: a promise was rejected with a non-error: [object Error] #990

ColCh opened this issue Feb 3, 2016 · 29 comments

Comments

@ColCh
Copy link

ColCh commented Feb 3, 2016

A warning is fired when I reject a Promise with an new Error('...'), but Promise and Error are from different Window 's (e.g. window.opener and window child - from window.open or iframe.contentWindow).

Of cource, it means, that rejectReason instanceof Error returns false, because Error is instance of iframe.contentWindow.Error (not of window.Error)

@ColCh
Copy link
Author

ColCh commented Feb 3, 2016

Upd: To hot-fixing this issue.

You can create another instance for Error and proxy all props from previous to new, and reject a promise with created Error.

            if (err) {
                const proxiedError = new Error();
                proxiedError.message = err.message;
                proxiedError.stack = err.stack;
                reject(proxiedError);
            } else {
                resolve(res);
            }

However, bluebird should use [[Class]] prop to determine errors (Object.prototype.toString), isn't it? Or I'm wrong?

@benjamingr
Copy link
Collaborator

What about error subclasses?
Honestly I'm fine with checking if it's possible to attach a stack to it.

@ColCh
Copy link
Author

ColCh commented Feb 3, 2016

For subclasses - same. Checked with EvalError and ReferenceError.

@benjamingr do you need a PR with failing test case?

@benjamingr
Copy link
Collaborator

Let's be more concrete. Can you show me a piece of code that takes something and determines if it's an error?

@ColCh
Copy link
Author

ColCh commented Feb 3, 2016

Sure. Consider following snippet:

export async function evalInWindowAsync (win, func/*nodeClb, jQuery*/, additionalArgs = []) {
    return new Promise((resolve, reject) => {

        let args = [];
        const argsId = _.uniqueId('evalInWindow_arg_holder_');

        const callback = function(err, res) {

            delete win[argsId];

            if (err) {
                const proxiedError = new EvalError();
                proxiedError.message = err.message;
                proxiedError.stack = err.stack;
                reject(proxiedError);
            } else {
                resolve(res);
            }
        };

        args.push(callback);
        args = args.concat(additionalArgs);
        win[argsId] = args;

        const evalArgs = [`window["${argsId}"][0]`, `window.jQuery`];

        for (var i = 1; i < args.length; i++) {
            evalArgs.push(`window["${argsId}"]["${i}"]`);
        }

        const functionString = _.isString(func) ? functionString : func.toString();
        const safeFunctionString = `function (done) {'use strict'; try { (${functionString}).apply(this, arguments); } catch (e) { done(e); } }`;
        const evalString = `;(${safeFunctionString})(${evalArgs.join(',')});`;
        win.eval(evalString);
    });
}

Code, which determines - resolve or reject a Promise. It's a Node-style callback.

        const callback = function(err, res) {

            // ...

            if (err) {
                const proxiedError = new EvalError();
                proxiedError.message = err.message;
                proxiedError.stack = err.stack;
                reject(proxiedError);
            } else {
                resolve(res);
            }
        };

And demo itself (snippet from a test case with chai-as-promised):

            const win = frame.contentWindow;

            const stub = function (done, $) { throw new Error('an error'); }

            return evalInWindowAsync(win, stub).should
                .eventually.be.rejectedWith('an error');

stub here becomes a string, get's evalled -> Error becomes frame.contentWindow.Error.
Test case passes, but bluebird fires warning in console.

@petkaantonov
Copy link
Owner

[[Class]] Doesn't work with subclasses:

function MyError() {
    Error.apply(this, arguments);
    Error.captureStackTrace(this, MyError);
}
MyError.prototype = Object.create(Error.prototype);
MyError.prototype.constructor = Error;

var error = new MyError();
({}.toString.call(error)) //[Object object]

@petkaantonov
Copy link
Owner

@benjamingr I guess we could check for presence of .name, .message and .stack

@ColCh
Copy link
Author

ColCh commented Feb 3, 2016

@petkaantonov almost forgot about that. Sorry for misinfo

@mjomble
Copy link
Contributor

mjomble commented Feb 10, 2016

I'm also getting the "promise was rejected with a non-error" warning for regular new Error(...) type errors when the code runs inside PhantomJS with Bluebird 3.x.

It worked fine with Bluebird 2.9.x

@sukima
Copy link
Contributor

sukima commented Mar 21, 2016

For anyone who finds this though hair pulling and Google. This bug is also relevant for cases in Node.js that use vm.runInThisContext. Spent two days trying to diagnose why my custom Error object's prototype was undefined! After deleting several console.logs I put in Bluebird source and then upgrading to 3.3.4 it all got fixed thanks to commit e77224c.

@liorcode
Copy link

liorcode commented May 1, 2016

Checking if message and name are string (e77224c) causes a new bug since 'message' is actually optional. so now rejecting with simply "new MyError()" (my error extends Error..) doesn't work anymore (causes the "rejected with non error [object Error]" warning). I realize it is difficult to effectively check if an object is an error, so how about trying both the old way and the new:

function isErrorLike(obj) {
    return obj !== null &&
           typeof obj === "object" &&
           typeof obj.message === "string" &&
           typeof obj.name === "string";
}

function canAttachTrace(obj) {
    return (obj instanceof Error || isErrorLike(obj)) && es5.propertyIsWritable(obj, "stack");
}

what do you think?

@benjamingr
Copy link
Collaborator

@petkaantonov ^ sounds good to me generally.

@petkaantonov
Copy link
Owner

Even if you don't pass message, it's still the empty string. Maybe your transpiler doesn't do subclassing correctly?

@BridgeAR
Copy link
Contributor

BridgeAR commented May 2, 2016

@petkaantonov subclass is often done like this:

// Example for Node.js
const util = require('util');

function SubError (message) {
    Error.captureStackTrace(this, this.constructor);
    // Stuff
    this.message = message; 
}
util.inherits(SubError, Error);

var err = new SubError ();
console.log(typeof err.message); // => undefined

Most people probably don't set a default value in such a case.

@petkaantonov
Copy link
Owner

That's not a subclass (it would not be instanceof Error) and captureStackTrace is proprietary.

@benjamingr
Copy link
Collaborator

So what if it's proprietary? Can we detect it?

@benjamingr
Copy link
Collaborator

Can't we just check for .stack?

@BridgeAR
Copy link
Contributor

BridgeAR commented May 2, 2016

I updated my example to inherit from Error. I implied a Node.js inheritance.

@petkaantonov
Copy link
Owner

You forgot to call super constructor, captureStackTrace doesnt do it and doesnt work in all browsers

@petkaantonov
Copy link
Owner

.stack is non standard so no

@wayofthefuture
Copy link

            .fail((xhr, err) => {
                let proxy = new Error();
                proxy.message = err || 'error is null';
                proxy.name = 'ajax error';
                reject(proxy);
            });

@gabegorelick
Copy link
Contributor

Apologies for commenting on an old issue, but @BridgeAR's comment still holds for ES classes. Here's an example from Sequelize:

class BaseError extends Error {
  constructor(message) {
    super(message);
    this.name = 'SequelizeBaseError';
    this.message = message;
    Error.captureStackTrace(this, this.constructor);
  }
}

Now, why they have this.message = message is a good question (perhaps a holdover from when this code didn't use ES classes?). But the fact is there's actual code out there that instantiates Errors like this that will cause Bluebird to issue spurious warnings, even though new BaseError() instanceof Error.

@petkaantonov Would you reconsider adding back the obj instanceof Error as an additional check? Something like:

function isError(obj) {
    return obj instanceof Error ||
           (obj !== null &&
           typeof obj === "object" &&
           typeof obj.message === "string" &&
           typeof obj.name === "string");
}

Happy to open a PR.

gabegorelick added a commit to gabegorelick/sequelize that referenced this issue Oct 2, 2017
The way Sequelize instantiates some of its Error subclasses
confuses Bluebird into thinking that Promises are rejected
with non-Error objects.

See petkaantonov/bluebird#990
@petkaantonov
Copy link
Owner

@gabegorelick yeah should be no harm in it

gabegorelick added a commit to gabegorelick/bluebird that referenced this issue Oct 3, 2017
janmeier pushed a commit to sequelize/sequelize that referenced this issue Oct 4, 2017
The way Sequelize instantiates some of its Error subclasses
confuses Bluebird into thinking that Promises are rejected
with non-Error objects.

See petkaantonov/bluebird#990
@jaas88
Copy link

jaas88 commented Aug 8, 2018

I am experiencing this issue using aurelia-http-client. Even configure the client using a HttpInterceptor returning a new Error per errorCode case, I still see the warning un browser console.

I am not able to remove this warning.
Help is appreciate.

@benjamingr
Copy link
Collaborator

@jalmansa88 well - for start you can turn it off directly, it's very unlikely we'll be able to help you without a MCVE

@atifsyedali
Copy link

Is there a way to turn off just this specific warning but not other warnings?

@benjamingr
Copy link
Collaborator

@jaas88
Copy link

jaas88 commented Aug 18, 2018

Thanks @benjamingr

@atifsyedali
Copy link

The only warning suppression flag I see there is for when you forget to return a promise.

wForgottenReturn is the only warning type that can be separately configured. The corresponding environmental variable key is BLUEBIRD_W_FORGOTTEN_RETURN.

Maybe I am missing something? In any case, I only get the warning on startup when es6-shim tries to initialize and test the features supported by the Promise impl in the env. See https://github.com/paulmillr/es6-shim/blob/8583cbe2186ec12bfbf4218ccbce7fc73f96de87/es6-shim.js#L2654

I'll suppress this myself so no worries. Just wanted to know if there is a way to turn it off already.

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

No branches or pull requests