Why does .then() work without a promise in JavaScript?

The then() method returns a Promise. See docs.

A promise has a handler method. Once a Promise is fulfilled or rejected, the respective handler function will be called asynchronously. The behavior of the handler function follows a specific set of rules as stated here.

Let's go over them one by one. Here is the code we will inspect side by side. Its nothing special, just a chain of promises returning values.

let sequence = new Promise(function (resolve) {
  console.log('Say 1')
  resolve(1)
})

sequence
  .then(() => {
    console.log('Say 2')
    return 2
  })
  .then(() => {
    console.log('Say 3')
  })
  .then(() => {
    console.log('Say 4')
    return Promise.resolve(4)
  })
  .then(() => {
    return new Promise(function (resolve) {
      console.log('Say 5')
      setTimeout(() => { resolve(5) }, 1000)
    })
  })
  1. returns a value, the promise returned by then gets resolved with the returned value as its value;

In code, this is Say 2, and your original question. When a value is returned, then() returns a Promise which is resolved with the value your returned.

  1. doesn't return anything, the promise returned by then gets resolved with an undefined value;

same as above.

  1. throws an error, the promise returned by then gets rejected with the thrown error as its value;

same as above, except now then() returns a Promise which is rejected with your error.

  1. returns an already resolved promise, the promise returned by then gets resolved with that promise's value as its value;

In code this is Say 4, where the promise has already been resolved. So now then() returns a Promise which is resolved with the value 4.

  1. returns an already rejected promise, the promise returned by then gets rejected with that promise's value as its value;

same as above, except it now rejects.

  1. returns another pending promise object, the resolution/rejection of the promise returned by then will be subsequent to the resolution/rejection of the promise returned by the handler. Also, the value of the promise returned by then will be the same as the value of the promise returned by the handler.

In code, this is Say 5. If you return a promise which has not been resolved yet, then() will return a Promise with the results of your promise i.e. 5.

One thing to note, that I also actually learned recently (suggested by @Bergi in comments) was that the then() method always constructs and returns a new Promise before the chain callbacks have even started to execute. The callbacks that you pass to then() simply tells the promise the value/error that the promise should resolve/reject with.

In summary, that is why then() chaining works even when you don't specifically return a new Promise - because then() method always constructs a new promise behind the scenes and rejects/resolves that promise with the value you returned. The most complex case in above scenarios is when you return a Promise in your callback, in which case your callback promise's results are passed to the then() promise.


It has to do with the promise chain, it doesn't matter if subsequent calls to then() are not promises, they are all part of the promise chain, the good thing is you can continue chaining promises, which allows you to do several async/promise operations in a row (as your example described), here is a real world example:

  // This is the generic http call
  private callServer(url: string, method: string, body?: any) {
    const uri = env.apiUrl + url;
    const session = this.sessionToken;

    const headers = {
      'Content-Type': 'application/json',
      'credentials': 'same-origin',
      'x-auth-token': session,
    };

    return fetch(uri, {
      method,
      headers,
      body: JSON.stringify(body),
    })
      .then(this.wait) // this is a timer, returns a promise
      .then(this.checkStatus) // this is a sync call
      .then((r) => r.text()) // r.text() is async
      .then((tx) => tx ? JSON.parse(tx) : {}); // this is sync
  }

You can read more about the promise chain here