0

When chaining multiple then statements, I'm struggling to understand when I need to return a value to the next then statement vs when it's automatically passed down. The confusion (for me) is when I have a promise inside a then statement vs not.

This is in a node environment - an express app (more specifically, a Firebase Function triggered by a HTTP request) - so I'll ultimately res.send() some value.

// Do I need to return mainFunction()?
mainFunction()
  .then(resultOfMyFunction => {
    // I want the next "then" to wait for the response from this block
    // Do I have to return asyncFunction() or just the value below?
    asyncFunction().then(resultOfPromise => {
      // Do I return resultOfPromise?
    }).catch(error => {
      // If I return this error, will it go to the mainFunction catch block?
      return error
    })
  }).then(resultOfPromise => {
    // This is blocking, so the next "then" should wait for the value
    return synchronousFunction(resultOfPromise)
  }).then(resultOfSynchronousFunction => {
    // End of function - do I need to return resultOfSynchronousFunction?
  }).catch(error => {
    // Do I need to return error?
  })

I know we shouldn't nest promises, but Firebase doesn't really give us an option when you need to chain multiple, different database calls where each call is a promise and you need to wait for data from one to call the next.

Gilad Bar
  • 1,302
  • 8
  • 17
I'm Joe Too
  • 5,468
  • 1
  • 17
  • 29
  • The second outer `.then()` will not wait for `asyncFunction()` to complete if you don't return it from the first outer `.then()`. – Patrick Roberts Nov 26 '18 at 15:31
  • The answer is don't nest promises. Chain them. if you do `return asyncFunction();` then the `then` can live outside of the function and it's easier to read – Liam Nov 26 '18 at 15:32
  • So I would need to return `resultOfPromise`, but do I also need to add `return` before `asyncFunction`? Or will the second outer then wait for `asyncFunction` automatically? – I'm Joe Too Nov 26 '18 at 15:32
  • *"will it wait automatically"*...No. it will run that function and immediately run the next `then()` where `resultOfPromise` will be undefined – charlietfl Nov 26 '18 at 15:33
  • `synchronousFunction` seems problematic. If this doesn't return a promise you need to wrap it in one if you want to consume if in this manner. – Liam Nov 26 '18 at 15:35
  • @Liam - are you saying this can be rewritten as `mainFunction().then(resultOfMyFunction => {return asyncFunction()}).then(resultOfPromise)` – I'm Joe Too Nov 26 '18 at 15:36
  • Many of the questions you ask here should be answered when reading a tutorial on promises, no? – trincot Nov 26 '18 at 15:42

3 Answers3

2

The confusion (for me) is when I have a promise inside a then statement vs not.

Whenever you have a promise inside a then or an async function you must return to it. Otherwise rejections (thrown errors in async functions and promises) get swallowed and end up in the global hook without giving a chance to whomever is calling your code to handle the error.

Synchronous functions have no such limitation - returning a synchronous code only matters if you want to reuse its return value since thrown errors are handled by the then automatically.

  // If I return this error, will it go to the mainFunction catch block?

Promises are really quite non-magical. The return value is how the error/success (rejected/fulfilled) state is propagated between calls so if you don't return the value - mainFunction won't go to its catch block.

If you want it to go to its catch block - the return value needs to be a promise that eventually rejects - for that you need to either rethrow the error in the inner .catch block or remove the .catch from there altogether.

Benjamin Gruenbaum
  • 270,886
  • 87
  • 504
  • 504
  • Appreciate the help! I was stuck on how to un-nest promises and the effect of synchronous functions inside `then` statements, but I get it now thanks to you and Liam's example. – I'm Joe Too Nov 26 '18 at 15:48
  • @trincot not misleading - just wrong, I missed that in the question. Fixing, thanks. – Benjamin Gruenbaum Nov 26 '18 at 15:53
  • @BenjaminGruenbaum: Could you provide a link or explanation why a promise inside a then (or async function) needs to be returned? I currently tried to "dispatch" a Promise whose result isn't of interest for me. So I just called it inside my async function without awaiting it (but try-catched it to log the error). The result is that my app just crashes without any error. And I really would like to know the mechanics behind the scenes. Thanks in advance! – Newlukai Apr 05 '19 at 08:44
  • That sounds weird and like some place in your code is throwing an error outside the call stack (like in a timer) - I'd recommend looking at the stack trace. – Benjamin Gruenbaum Apr 05 '19 at 10:30
  • Thanks for your support. And yes. After some changes I noticed that there is an error thrown. It is confusing that I don't get the usual "UnhandledPromiseRejection". Inside a `then` I call a function which returns a Promise. I add a catch call and don't return it. I was hoping that thus I would handle the rejection but don't need to wait for the Promise settlement to continue the outer Promise chain. – Newlukai Apr 05 '19 at 11:46
0

You don't really need the anonymous functions in the first two then()

You can do it by passing function references to then() instead

mainFunction()
  .then(asyncFunction)
  .then(synchronousFunction)
  .then(resultOfSynchronousFunction => {
    // consume resultOfSynchronousFunction or return to next then()
  }).catch(error => {
    // Do I need to return error?
    // only if this is not final catch block
  })
charlietfl
  • 170,828
  • 13
  • 121
  • 150
-1

I think you want something like this. I'm presuming that by This is blocking you mean synchronousFunction is not async. In which case it does not need to be consumed in a promise like fashion.

mainFunction()
  .then(resultOfMyFunction => {
    //return promise from async function
    return asyncFunction();
  })
  .then(resultOfPromise => {
    let resultOfSynchronousFunction = synchronousFunction(resultOfPromise);
    // End of function
  })
  .catch(error => {
    // Do I need to return error?
  });

if you want to return resultOfSynchronousFunction then you should read How do I return the response from an asynchronous call?

If you want to return an async version of synchronousFunction you'll need to wrap this in a promise:

mainFunction()
  .then(resultOfMyFunction => {
    //return promise from async function
    return asyncFunction();
  })
  .then(resultOfPromise => {
    return new Promise((resolve, reject) => {
       let resultOfSynchronousFunction = synchronousFunction(resultOfPromise);
       // End of function
       resolve(resultOfSynchronousFunction);
    });
  })
  .then(resultOfSynchronousFunction => {})
  .catch(error => {
    // Do I need to return error?
  });

If you want to run another async call after synchronousFunction a better pattern would be:

mainFunction()
  .then(resultOfMyFunction => {
    //return promise from async function
    return asyncFunction();
  })
  .then(resultOfPromise => {

       let resultOfSynchronousFunction = synchronousFunction(resultOfPromise);
       // End of function
       return anotherAsyncCall(resultOfSynchronousFunction );
  })
  .then(resultOfanotherAsyncCall => {})
  .catch(error => {
    // Do I need to return error?
  });
Liam
  • 27,717
  • 28
  • 128
  • 190
  • But if I want to use the result of `synchronousFunction` in the next `then` statement, I can `return synchronousFunction(resultOfPromise)` and the next `then` will be blocked until `synchronousFunction` is done? – I'm Joe Too Nov 26 '18 at 15:40
  • 2
    In your code there is no next `then`? Promises (async functions) do not block. synchronous functions block. I'm not really clear what your getting at? – Liam Nov 26 '18 at 15:41
  • Sorry. If I want to use `resultOfSynchronousFunction` in another `then` block (let's say I have three more `then` statements to run, that very next `then` block will wait until `synchronousFunction` is complete and I return that to the next `then`? – I'm Joe Too Nov 26 '18 at 15:44
  • You'll need to wrap it in a promise, I've added code for you to show this. Note that `synchronousFunction` will never block in this scenario – Liam Nov 26 '18 at 15:44
  • Got it! So everything inside a `then` statement must be a promise, and I can return the async function and pull my `then` and `catch` up so I'm not nesting. Thank you - it's clear now! – I'm Joe Too Nov 26 '18 at 15:46
  • I was about to upvote this and then I saw you added the `new Promise` inside the `then` stuff which doesn't actually do anything other than create a superflous promise and assimilate it. – Benjamin Gruenbaum Nov 26 '18 at 15:47
  • 3
    I did not downvote, but wrapping the synchronous function in a `new Promise` is an antipattern. – trincot Nov 26 '18 at 15:48
  • OP seemed quite clear that they wanted this. I'm not clear why either but `¯\_(ツ)_/¯` *If I want to use resultOfSynchronousFunction in another then block* – Liam Nov 26 '18 at 15:48
  • You can use a synchronous function in another then block or wrap it in a Promise.resolve instead of a `new Promise` – Benjamin Gruenbaum Nov 26 '18 at 15:50
  • 1
    @Liam is right about what I was asking. How would I use the result of a synchronous function and pass that to the next `then` statement if not via a promise? – I'm Joe Too Nov 26 '18 at 15:50
  • @Joe-NewNine I'm guessing you have other async blocks? I've added a cleaner implementation which addresses the (correct) anti pattern comments – Liam Nov 26 '18 at 15:52
  • You can just return the value of the synchronous function. You can return *any* value in a `then` callback, even if you don't it will be the `undefined` value... whatever. The `then` method had already created a promise, so it just fulfills with that value and initiates the next `then` in the chain. The special case is in fact when you *do* return a promise in a `then` callback. ;-) – trincot Nov 26 '18 at 15:55
  • I got it now. Thanks for the help all! – I'm Joe Too Nov 26 '18 at 15:56