red
red

Reputation: 2040

Why does the Promise chain work in this example, without throwing an error

So in the first example, the chain executes all the way to the last console.log, without an error, but in the second example, which is similar, it does error out. Why does this happen and can I somehow make the first example also throw an error?

I only know it has to do with adding functions to an object, because if test would be undefined, the chain would break, but I cannot understand how accessing a non-existant key in an object doesn't cause an error.

Edit: I just realized my brainfart. Naturally accessing test.something returns undefined instead of an error.

// EXAMPLE 1: Weird

var test = {};

test.f1 = function(result) {
    return new Promise(function(resolve, reject) {
        console.log("result", result);
        resolve(1)
    });
}

test.f2 = function(result) {
    return new Promise(function(resolve, reject) {
        console.log("result", result);
        resolve(2)
    });
}

test.f3 = function(result) {
    return new Promise(function(resolve, reject) {
        console.log("result", result);
        resolve(3)
    });
}

test.f4 = function(result) {
    console.log("result", result);
}

test.f1(1)
.then(test.f2)
.then(test.f3)
.then(test.shouldCauseError)
.then(test.f4)
// EXAMPLE 2: As expected

new Promise(function(resolve, reject) {
    resolve(1);
})
.then(function(result) {
    return new Promise(function(resolve, reject) {
        resolve(2)
    });
})
.then(function(result) {
    return new Promise(function(resolve, reject) {
        resolve(3)
    });
})
.then(shouldCauseError)
.then(function(result) {
    console.log("result:", result);
});

Upvotes: 1

Views: 47

Answers (2)

FZs
FZs

Reputation: 18619

In a .then listener, both arguments are optional. That is, the following is completely valid:

Promise.resolve().then() //Nothing passed to then

Which is the same as if you write:

Promise.resolve().then(undefined, undefined)

As accessing objects' non-existent properties return undefined, so...

.then(test.shouldCauseError)

...is essentially the same as...

.then(undefined)

...and it doesn't throw.


In the second case, however, it does throw, but it has nothing to do with .then: Accessing a non-existent variable throws a ReferenceError.

So the following also throws:

shouldCauseError

...before shouldCauseError's value could even be passed to .then

Upvotes: 1

CertainPerformance
CertainPerformance

Reputation: 370679

.then accepts up to two arguments. The first argument, if it exists, runs if the upper Promise resolved. The second argument, if it exists, runs if the upper Promise rejected.

Passing undefined as the first argument (or as both arguments) to .then is permitted - since no arguments exist, the upper Promise will be passed through the .then unchanged.

(also, .then(undefined, fn) is equivalent to .catch(fn))

So, your

.then(test.shouldCauseError)

passes undefined to .then, which means that the upper Promise is passed through the .then without modification (and without throwing) - it goes to the next .then in the chain, as if .then(test.shouldCauseError) wasn't there.

Since passing undefined to .then is perfectly fine, you need some other method to create an error - perhaps wrap everything you pass to .thens in a function that checks if the passed expression is actually a function, and if not, returns a function that throws:

.then(validate(test.shouldCauseError))

// EXAMPLE 1: Weird

var test = {};

test.f1 = function(result) {
    return new Promise(function(resolve, reject) {
        console.log("result", result);
        resolve(1)
    });
}

test.f2 = function(result) {
    return new Promise(function(resolve, reject) {
        console.log("result", result);
        resolve(2)
    });
}

test.f3 = function(result) {
    return new Promise(function(resolve, reject) {
        console.log("result", result);
        resolve(3)
    });
}

test.f4 = function(result) {
    console.log("result", result);
}

const validate = arg => {
  if (typeof arg !== 'function') {
    return () => {
      throw new Error();
    }
  }
  return arg;
};

test.f1(1)
  .then(validate(test.f2))
  .then(validate(test.f3))
  .then(validate(test.shouldCauseError))
  .then(validate(test.f4))
  .catch((err) => {
    console.log('Error caught');
  });

You could also make test a Proxy that returns a throwing function when a property is accessed which doesn't exist on the target object.

But these are both weird solutions, because the "problem" being solved isn't considered a problem in JS. If you want to avoid these sorts of mistakes, I'd prefer to use something like Typescript to ensure that what I'm passing is of the right type, and not undefined.

Upvotes: 2

Related Questions