Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the difference between then and finally in a promise?

Tags:

I see the docs for Bluebird's finally but I still don't quite understand the difference vs. then.

To be clear: I know exactly why then gets called after a catch. I want it to be called after a catch. That's the intent. My question is: If I want code to always be executed regardless of the promise state, what's the difference between then vs finally for that?

I built this test:

var Promise = require("bluebird");  function test1 () {     console.log("RESOLVE + THEN + CATCH + THEN");     return new Promise((resolve, reject) => resolve())        .then(() => console.log("then"))        .catch(err => console.log("error:", err.message))        .then(() => console.log("end")); }  function test2 () {     console.log("REJECT + THEN + CATCH + THEN");     return new Promise((resolve, reject) => reject(new Error("rejected")))        .then(() => console.log("then"))        .catch(err => console.log("error:", err.message))        .then(() => console.log("end")); }  function test3 () {     console.log("RESOLVE + THEN + CATCH + FINALLY");     return new Promise((resolve, reject) => resolve())        .then(() => console.log("then"))        .catch(err => console.log("error:", err.message))        .finally(() => console.log("end")); }  function test4 () {     console.log("REJECT + THEN + CATCH + FINALLY");     return new Promise((resolve, reject) => reject(new Error("rejected")))        .then(() => console.log("then"))        .catch(err => console.log("error:", err.message))        .finally(() => console.log("end")); }  // run tests "sequentially" so console output doesn't get blended setTimeout(test1, 500); setTimeout(test2, 1000); setTimeout(test3, 1500); setTimeout(test4, 2000); 

This tests four cases:

  1. .then(...).catch(...).then(...) with a resolved promise.
  2. .then(...).catch(...).then(...) with a rejected promise.
  3. .then(...).catch(...).finally(...) with a resolved promise.
  4. .then(...).catch(...).finally(...) with a rejected promise.

The results I see are cases 1+2 behaves identically to 3+4: The last bit (then or finally depending on test) executes no matter what happens before it, as intended. The output of that program is:

RESOLVE + THEN + CATCH + THEN then end REJECT + THEN + CATCH + THEN error: rejected end RESOLVE + THEN + CATCH + FINALLY then end REJECT + THEN + CATCH + FINALLY error: rejected end 

Now, the reason I'm asking is because I saw a comment on this other question I asked:

Not sure if your promises support it, but you should change the last .then to .finally so that the busy always gets cleared.

From my very limited knowledge of then, and the tests above, it seems like then is sufficient. But after that comment I'm questioning myself and the safety of using then to execute "finally" code.

So my question is: What's the difference between then and finally? They look like they behave the same, but when would I need to use finally instead of then?

like image 958
Jason C Avatar asked Dec 13 '16 20:12

Jason C


2 Answers

First difference: Sometimes you don't want to catch errors at the place they arise, but in the code that uses this function, so you don't catch them. In that case you can't substitute then() and finally().

Sometimes you have to clean something up whether there was an error or not (nulling references, clearing timeouts ... stuff like that). That's where you use finally().

Second difference: The function you pass to catch() could also throw, then you would have a rejected Promise and the following then() would not be called.

(so a finally before a catch will still execute on an error, didn't know that)

Yeah, that's the point of finally(). It will be executed under any circumstance without changing the resolved value.

You might want to read/google a bit about try {} finally {}, without catch.

like image 156
Thomas Avatar answered Nov 24 '22 12:11

Thomas


.then and .finally are not the same.

.then is the main promise primitive. It's what is defined, thoroughly, in the Promises/A+ spec and all promise libraries will implement it.

A Bluebird .finally handler will "be called regardless of the promise's fate". So an unhandled exception still triggers a .finally.

new Promise((resolve, reject) => reject(false))   .finally(a => console.log('finally', a)) // finally undefined // Unhandled rejection false  new Promise((resolve, reject) => reject(false))   .then(a => console.log('then', a)) // Unhandled rejection false 

.finally won't change the resolved value of the promise and doesn't receive the result of the promise chain.

new Promise((resolve, reject) => reject(false))   .catch(e => {     console.log(e)     return 2   })   .finally(a => {     console.log('finally', a)     return 1   })   .then(res => console.log('res', res)) // finally undefined // res 2 

The methods look similar in your test cases as the tests catch all errors and you are only using promises for flow control, not relying on the values being resolve/rejected along the promise chain.

like image 23
Matt Avatar answered Nov 24 '22 11:11

Matt