Search code examples
javascriptasynchronouses6-promise

How to use fetch and catch all errors in js?


I have a fetch() call in function myFetch() (returns the fetch's return value), and would like to wrap it in another function callApi(). My goal is to make callApi() return a Promise, such that:

  • A "resolve" state indicates success (the fetch call returned response.ok / HTTP 200).
    • In this case, I would like the value (of promise) to be the body/text of the response.
  • A "reject" state indicates any failure during the process, which could be:
    • There is something wrong with the fetch call, e.g. a GET request with body.
      • In this case, I would like the message of reject to be that error.
    • The fetch succeeded, but the upstream response is not HTTP 200.
      • In this case, I would like the message of reject to be status_code: body where status_code and body are both from the HTTP response.
      • In fact, it does not have to be in this form, as long as the caller of callApi() can reconstruct this same message.

However, I do not know how to write the callApi() function. My attempt only ended up with this (where myFetch() is the call to fetch):

    return new Promise(
        (resolve, reject) => {
            myFetch()
                .then((response) => {
                    if (response.ok) {
                        resolve(response);
                    } else {
                        reject(`${response.status}: ${response.text}`);
                    }
                }, (error) => {
                    reject(error);
                });
        }
    );

As you see, response.text is a Promise, but I need the string there. How can I achieve my goal?

In addition, if I also need to receive JSON response (from the fetch), will there be any difference?


Solution

  • You shouldn't create a new promise with new Promise when you already have a promise -- the one returned by myFetch.

    Secondly, you'll need to call the text method on the response, and construct the string when that returned promise resolves.

    It may be easier to use async await syntax:

    async function myApi() {
        try {
            const response = await myFetch();
            const body = await response.text();
            return response.ok ? body : `${response.status}: ${body}`;
        } catch(e) {
            return e;
        }
    }    
    

    Here the returned promise will never reject. If you want the promise to reject unless the response status is ok, then leave out the try..catch wrapper, and do:

    async function myApi() {
        const response = await myFetch();
        const body = await response.text();
        if (!response.ok) throw `${response.status}: ${body}`;
        return body;
    }
    

    When you want to use the json method instead of text, then you'd want to produce an object instead of a string, and then it might be a better idea to always return an object that has the same toplevel keys:

    The version that will never return a promise that rejects:

    async function myApi() {
        let ok = false, error = true, status;
        try {
            const response = await myFetch();
            ({ ok, status }) = response;
            body = await response.json();
            error = false;
        } catch(e) {
            body = e;
        }
        return { ok, error, status, body };
    }    
    

    The version that will reject the promise when the response is not ok:

    async function myApi() {
        let ok = false, error = true, status;
        try {
            const response = await myFetch();
            ({ ok, status }) = response;
            body = await response.json();
            error = false;
        } catch(e) {
            body = e;
        }
        if (!ok) throw { error, status, body };
        return { error, status, body };
    }