1

After watching the video, and reading in [Promise.then][1] i still don't understand how it works. So i wrote an example, and printed to console but still with the output i can't understand how does it work. I hope my question are clear enough, if not please tell me and i will try to elaborate them according to your responses. This is my code:

   const getCountryData = function (country) {

   fetch(`https://restcountries.eu/rest/v2/name/${country}`)
     .then(response => {
       console.log('111111111111111');
       if (!response.ok)
         throw new Error(`Country not found (${response.status})`);
       return response.json();
       })
     .then(data => {
         console.log('22222222222');
        const neighbour = data[0].borders[0];
       if (!neighbour) return;
       return fetch(`https://restcountries.eu/rest/v2/alpha/${neighbour}`);
     })
     .then(response => {
         console.log('333333333')
       if (!response.ok)
         throw new Error(`Country not found (${response.status})`);
       return response.json();
        })
     .then(data => console.log('44444444444'))
     .catch(err => {
       console.error(`${err} `);
     })
     .finally(() => {
       console.log('finalyyyy')
     });
     
     console.log("after all async calls");
 };

I can't understand the following things:

  1. If we look for example on the first then, it gets a callback as a parameter, the callback returns a Promise object. Which object calls the second then? The one that was returned from the first call to when or the one that have been returned from the callback?
  2. I understand that the callback is "attached" to the fetch function. e.g when the fetch function will be finished, the callback will be executed. But i can't understand how and when the second, third and fourth then are called. The first callback contains also an async function ** response.json()**, so how and when exactly the second when will be started if he need the json.response from the first when?
  3. If an exception occur at the first or second, how exactly the code knows to jump to catch function?
    [1]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then
Eitanos30
  • 1,331
  • 11
  • 19
  • 1
    "*Which object calls the second then?*" - no "object" is calling it. *Your code* is calling the method, on an object - and in your code that is the promise returned by the first `.then()` call. "*the one that have been returned from the callback?*" - the callback was not yet executed when the second `.then()` is called, so it can't be the return value – Bergi Jan 04 '21 at 17:33
  • Maybe have a look at https://stackoverflow.com/a/22562045/1048572 – Bergi Jan 04 '21 at 17:33
  • https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises#Chaining – ISAE Jan 04 '21 at 17:36
  • I read both links and still can't understand some stuff. 1.Do all `when` methods are executed immediately without to wait the callback to be run first? 2. How one callback returned value is the second one callback input (not exactly since it takes the values out of the 'Promise')? – Eitanos30 Jan 04 '21 at 17:49
  • @Eitanos30 Yes, `then()` calls, promise creation, and chaining happens immediately, without waiting for anything. They just install handlers. Later, when the promise settles, the callbacks are executed, and may resolve further promises, which cause their callbacks to be executed and so on. – Bergi Jan 04 '21 at 17:56
  • @Eitanos30 For understanding how promises "unwrap" (rather, how inner promise results are used to resolve outer promises), maybe have a look at [the implementation of `then`](https://stackoverflow.com/a/15668353/1048572) – Bergi Jan 04 '21 at 17:58
  • @Bergi, thanks i really tried but this link is to hard for me now :( thanks again – Eitanos30 Jan 04 '21 at 18:06
  • @Eitanos30 What exactly is left unclear? – Bergi Jan 04 '21 at 18:45
  • "*how exactly the code knows to jump to catch function?*" - code doesn't jump. A rejection handler (as installed by `.catch()`) is called when the promise is rejected. And on a `.then()`, the result promise is rejected if the input promise gets rejected, so the error simply propagates down the chain step by step - no jumping. – Bergi Jan 04 '21 at 18:47
  • @Bergi, if first throw statement happens, what exactly happens in the second and third when's callback? And in addition i'm breaking my head to understand how the callback "knows" to run only after the promise was settled (is it part of `Promise` mechanism) ? – Eitanos30 Jan 04 '21 at 21:14
  • Yes, it's part of the promise mechanism. A promise is like a one-time event emitter in that regard - when you resolve/reject it the handlers that were registered are called back. – Bergi Jan 04 '21 at 21:20
  • @Bergi, thanks for the help.. It's really difficult to me. I can't understand what is the type of the argument in the `then` callback method? Is it the type of the return statement of the previous `then` callback? Because in my example `response.json` return a `Promise` object, but in the following `then` it is written *const neighbour = data[0].borders[0];*, but *Promise* object doesn't have an array of data... So how is it possible? Do you want me to open a new post for this question? – Eitanos30 Jan 04 '21 at 22:35
  • 1
    It is the value that the promise is fulfilled with, which *generally* what was passed to the promise's `resolve()` function. And that depends on how the promise was constructed. If it's a promise constructed by (returned from) a `.then(res => …)` call, yes, it's resolved with the return value of the callback. However, [resolving means that promises are recursively unwrapped](https://stackoverflow.com/a/29269515/1048572), so if you return a promise from the `then` callback the outer promise will eventually fulfill with the inner promise's *result*. – Bergi Jan 04 '21 at 22:41
  • @Bergi, so how does `const neighbour = data[0].borders[0]` is valid if data is of `Promise` type? i read in the documentation that `Respons.json` returns a `Promise` object – Eitanos30 Jan 04 '21 at 23:03
  • No, `data` is not a promise, it's the result of that promise. – Bergi Jan 05 '21 at 12:54
  • but you said :`If it's a promise constructed by (returned from) a .then(res => …) call, yes, it's resolved with the return value of the callback`. and the first `then` returns a **response.json();** which is **Promise**, doesn't it means that the callback parameter (named `data` in the example) of the `second then` is from the **response.json();** (which is Promise)? – Eitanos30 Jan 05 '21 at 13:02
  • Resolving is not the same as fulfilling. When you resolve with a promise, you ultimately fulfill with the fulfillment value of that promise (once it fulfills). A promise can never be fulfilled with another promise, and a `then` callback is never called with a promise. – Bergi Jan 06 '21 at 15:28
  • @Bergi, thank you very much. Unfortunately it still doesn't feel that you refer to my last question. I don't want to make you too nervous and i really appreciate your huge effort. So Unfortunately i will live without the understanding.. I'm apologize for not being clever enough. Thanks again! – Eitanos30 Jan 06 '21 at 16:24

2 Answers2

0

The then functions don't receive a callback, they receive the result of the previous async operation. So the result of the fetch is passed to the first then, the result of response.json() is passed to the second then and so on...

In case of error the chain broke and it goes directly to the firs catch function.

Under the hood there are more, consider that the fetch and other operations in the then functions can be asynchronous, so what happens in the runtime is that the result of the fetch function is pushed in the event queue and when the event loop takes your next then function is extracted from the queue and passed to the function.
This article explain how it works internally: https://www.digitalocean.com/community/tutorials/understanding-the-event-loop-callbacks-promises-and-async-await-in-javascript

ema
  • 5,668
  • 1
  • 25
  • 31
  • I don't think this answers the question. The OP never considered `data` or `response` to be callback functions. – Bergi Jan 04 '21 at 17:34
  • I think this answer is a little bit misleading. `then` does register a callback, which is later called by the underlying scheduler when the `promise` resolves. The response isn't passed to `then` but to the registered callback – sinanspd Jan 04 '21 at 17:36
  • @sinanspd yes, ok, I was trying to simplify. In practice what happens is that the output of the first `then` in passed to the second `then`. – ema Jan 04 '21 at 17:37
  • @ema Yes, it doesn't work like that, but the OP never assumed it either so your answer doesn't need to point it that `then` functions don't receive callbacks. – Bergi Jan 04 '21 at 17:59
-1

I will try to explain this in a different way, using async/await operators.

The equivalent of your getCountryData function is as follows

const getCountryData = async (country) => {
    console.log("after all async calls"); // explanation for the this line below
    try {
        let response1 = await fetch(`https://restcountries.eu/rest/v2/name/${country}`);

        // first then
        console.log('111111111111111');
        if (!response1.ok)
            throw new Error(`Country not found (${response1.status})`);
        let data1 = await response1.json();

        // second then
        console.log('22222222222');
        const neighbour = data1[0].borders[0];
        let response2;
        if (neighbour)
            response2 = await fetch(`https://restcountries.eu/rest/v2/alpha/${neighbour}`);

        // third then
        console.log('333333333');
        if (!response2.ok)
            throw new Error(`Country not found (${response2.status})`);
        let data2 = await response2.json();

        // fourth then
        console.log('44444444444');
    } catch (err) {
        console.error(`${err} `);
    } finally {
        console.log('finalyyyy');
    }
}

First of all, you will see the console.log in your last line of function became the first line in my function. This is because the line is executed before the asynchronous function fetch completes.

As you can see, the last return variable from the fetch is available in the first then, and the last return variable from the then is available in the next then.

  1. So for your first question, who calls the callback function in the next then? Obviously you can see from the async/await equivalent of your function, it is executed in sequence. The return result from the previous then will be the argument for the next then. Therefore in the second then, when you do return;, it will not stop executing the third then, but instead the third then will reveive undefined as the response argument.

  2. As explained in answer to question 1, the then functions are executed in sequence automatically as long as there are no unhandled errors or rejected promise. However, for a second/third/forth then callback function to access the argument from the first then, it is not possible to access directly, unless you pass it along with every return from the subsequent then. This can be achieved easily by using then async/await operators instead of then.

  3. How exactly does the code knows to jump to the catch function when an error is thrown? In an asynchronous function, throwing an error is almost equivalent to rejecting a promise. Think of it this way, every then callbacks are wrapped in a try/catch block that would call the function in the catch.

Edit: If you do fetch().then().then().catch(), all the errors or rejected promises will be handled in the catch function. But if you do fetch().catch().then().then(), only the error or rejected promise from fetch will be handled in the catch function.

mylee
  • 1,293
  • 1
  • 9
  • 14
  • thanks but i can't understand what the callback returns. If the line ` let data1 = await response1.json();` returns a `Promise` object so how the line `const neighbour = data1[0].borders[0];` works? `data` variable is from `Promise` type, and `Promise` doesn't have `data` property – Eitanos30 Jan 05 '21 at 11:18
  • Explaining `then` in terms of `async`/`await` is mixing things up, as `await` desugars back to `then` calls. – Bergi Jan 05 '21 at 12:57
  • "*for a second then callback, it is not possible to access the argument from the first then directly*" - unless you [simply nest them](https://stackoverflow.com/a/28250687/1048572), which is exactly what `await` does internally – Bergi Jan 05 '21 at 12:59
  • "*It doesn't matter if you do `fetch().then().then().catch()` or `fetch().catch().then().then()`, they will work the same*" - no, absolutely not. – Bergi Jan 05 '21 at 13:00
  • @Eitanos30 the `await` keyword will wait for the Promise to resolve and the result from the promise will be assigned to the variable `data1` – mylee Jan 06 '21 at 02:49
  • @Bergi I don't agree with the usage of nesting `then` because it will just create more confusion with too many nesting. I do prefer using `await` instead of `then` for promises because it is much easier to read and understand. Thank you for the correction too, the position of `catch` does matter after all after I did a simple experiment with it. – mylee Jan 06 '21 at 02:59
  • @mylee, Can you please explain me something. the `response1.json()` that is assign to `data1`, is from `Response` type.. So how does data[] works? – Eitanos30 Jan 06 '21 at 10:54
  • @Eitanos30 `response1.json()` returns a `Promise`, and the `await` keyword is important here. Take for example the code `var data = await new Promise(resolve => resolve("my data"));`, which you can run it in the console to verify, the type for the variable `data` is string, not promise. – mylee Jan 06 '21 at 11:08
  • does `await` or `then` takes the result out from the `Promise`? i still can't figure it out – Eitanos30 Jan 06 '21 at 11:51
  • 1
    @Eitanos30 a simple answer is yes – mylee Jan 06 '21 at 12:46