How come resolving a settled Promise doesn't throw?

# /#!/JoePea (8 years ago)

f.e.

let resolve
let p = new Promise(r => resolve = r)

resolve(5) //  resolves the promise.
resolve(4) // noop (in Chrome), but why not throw an error?

I only tested in Chrome, and I'm assuming it follows spec, but I could be wrong.

I'm asking because it seems that throwing an error will prevent shots in the foot, so that code doesn't assume that resolving on an already resolved Promise worked, although it didn't. It seems like it can lead to unexpected failures.

# Tab Atkins Jr. (8 years ago)

On Tue, Feb 28, 2017 at 10:12 AM, /#!/JoePea <joe at trusktr.io> wrote:

f.e.

let resolve
let p = new Promise(r => resolve = r)

resolve(5) //  resolves the promise.
resolve(4) // noop (in Chrome), but why not throw an error?

I only tested in Chrome, and I'm assuming it follows spec, but I could be wrong.

I'm asking because it seems that throwing an error will prevent shots in the foot, so that code doesn't assume that resolving on an already resolved Promise worked, although it didn't. It seems like it can lead to unexpected failures.

That's correct behavior, yes. In general, it's because the internal state of a promise is meant to be unobservable unless you're specifically listening to it.

# Isiah Meadows (8 years ago)

Also, making promise resolution idempotent makes dealing with things way easier. Similarly, most deferred libraries ensure their resolution is idempotent.

# Jordan Harband (8 years ago)

That seems like it would allow synchronous observation of Promise state - consider:


function isResolved(promise) {
  try {
    new Promise((resolve) => {
      resolve(promise);
      resolve();
    });
  } catch (e) {
    return true;
  }
  return false;
}

# Jordan Harband (8 years ago)

Although now that I think about it, it wouldn't have to care about the promise state, resolve and reject could just throw if they're invoked more than once.

# Isiah Meadows (8 years ago)

BTW, there's still usefulness of making resolution/rejection idempotent and never throwing. IMHO, I'd just like to see this die.


Isiah Meadows me at isiahmeadows.com

# Andrea Giammarchi (8 years ago)

Throwing on already resolved/rejected will break the Web 'cause the following example pattern is quite common.

class Lie extends Promise {
  constructor(fn) {
    let _resolve, _reject;
    super((resolve, reject) => fn(
        _resolve = resolve,
        _reject = reject
    ));
    this._resolve = _resolve;
    this._reject = _reject;
  }
  reject(error) {
    this. _reject(error);
    return this;
  }
  resolve(how) {
    this._resolve(how);
    return this;
  }
  cancel(...reason) {
    this._reject(...reason);
    return this;
  }
}

// resolve in 5 seconds with 123
const p = new Lie((res, rej) => setTimeout(res, 5000, 'timeout'));

// however, something else could happen before / or after
setTimeout(() => p.resolve('hello there').then(console.log), 1000);

As summary, after years without throwing, I don't see why it should now.