skip to Main Content

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?

2

Answers


  1. To obtain the response body as a string, you can modify your code as follows:

      myFetch()
        .then((response) => {
          if (response.ok) {
            response.text().then((body) => {
              resolve(body);
            });
          } else {
            response.text().then((body) => {
              reject(`${response.status}: ${body}`);
            });
          }
        })
        .catch((error) => {
          reject(error);
        });
    });
    

    response.text() returns a Promise that resolves to the response body as a string. By chaining another .then() callback to it, you can access the body and pass it to either resolve or reject based on the response’s ok status.

    As for the JSON responses, you might want to use response.json() method instead of response.text(). This method returns a Promise, which resolves to the parsed JSON data instead of just text. Here’s an updated version of the code that handles JSON responses:

    return new Promise((resolve, reject) => {
      myFetch()
        .then((response) => {
          if (response.ok) {
            response.json().then((data) => {
              resolve(data);
            });
          } else {
            response.json().then((errorData) => {
              reject({
                status: response.status,
                body: errorData,
              });
            });
          }
        })
        .catch((error) => {
          reject(error);
        });
    });
    

    When the response isn’t successful, the reject function is called with an object containing the response status and the parsed JSON error body. This allows the caller of callApi() to access both the status code and the response body in a structured manner.

    More information:

    1. Fetch API: MDN Fetch API
    2. Response.text(): MDN Response.text()
    3. Response.json(): MDN Response.json()
    4. JavaScript Promises: MDN Promises
    Login or Signup to reply.
  2. 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 };
    }    
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search