2

I'm trying to write a wrapper function for the JavaScript fetch command.

I took the example code from this post:

function fetchAPI(url, data, method = 'POST') {
const headers = {
    'Authorization': `Token ${getAuthToken()}`,
};

return fetch(url, { headers, 'method': method, 'body': data })
    .then(response => {
        if (response.ok) {
            const contentType = response.headers.get('Content-Type') || '';

            if (contentType.includes('application/json')) {
                return response.json().catch(error => {
                    return Promise.reject(new Error('Invalid JSON: ' + error.message));
                });
            }

            if (contentType.includes('text/html')) {
                return response.text().then(html => {
                    return {
                        'page_type': 'generic',
                        'html': html
                    };
                }).catch(error => {
                    return Promise.reject(new Error('HTML error: ' + error.message));
                });
            }

            return Promise.reject(new Error('Invalid content type: ' + contentType));
        }

        if (response.status === 404) {
            return Promise.reject(new Error('Page not found: ' + url));
        }

        return response.json().then(res => {
            // if the response is ok but the server rejected the request, e.g. because of a wrong password, we want to display the reason
            // the information is contained in the json()
            // there may be more than one error
            let errors = [];
            Object.keys(res).forEach((key) => {
                errors.push(`${key}: ${res[key]}`);
            });
            return Promise.reject(new Error(errors)
            );
        });
    }).catch(error => {
        return Promise.reject(new Error(error.message));
    });

};

And I'm calling it like this:

fetchAPI('/api/v1/rest-auth/password/change/', formData).then(response => {
        console.log('response ', response);
    });

Edit: I have modified the code to display information returned by the server if the request is ok but refused, for example because of an invalid password. You have to interrogate the response json if ok == false.

A valid URL fetch is fine. But if there is an error, I see an Unhandled Rejection (Error): error message.

Why is it that the rejects are unhandled even though they are in catch blocks? What's the secret sauce here?

Little Brain
  • 2,647
  • 1
  • 30
  • 54
  • 2
    Your handling the error, only then to re-throw the error, that then doesn't get handled. aka. `Unhandled Rejection (Error)` ps. `.catch(error => { return Promise.reject(new Error(error.message)); });` does nothing really useful. – Keith Jan 07 '19 at 12:24
  • 1
    Have you tried adding a `.catch(...)` onto you calling code `fetchAPI(...).then(...).catch(...)` – Jamiec Jan 07 '19 at 12:27
  • @Keith, I've seen this format in many examples, is it fundamentally wrong and if so why is it so common? And how would you recommend handling error cases inside the fetchAPI function? What's the right way to notify the calling function of the error and pass it the error message? – Little Brain Jan 07 '19 at 13:42
  • @LittleBrain I dont think theres anything inherently wrong with the way you've done it, you just need to remember to handle the possible exceptions from the caller (or, accept the unhandled rejection message) – Jamiec Jan 07 '19 at 14:08
  • @Jamiec, thank you! It is confusing because the error appears to be in the fetchAPI function...well, it is, but it's the calling function that isn't handling it. That is much clearer to me now. – Little Brain Jan 07 '19 at 14:22
  • @LittleBrain Do you mean the ps. bit, and why its fundamentally wrong.. It's because your doing this -> `try { doSomething(); } catch(e) { raise e; }`, and this has the same meaning as `doSomething()`.. IOW: The try / catch is not doing anything useful. If you had done a console.log, or at least something other it might make sense,. But catching all errors to re-throw the same error doesn't make any sense. – Keith Jan 07 '19 at 14:27
  • HI @Keith, the idea is to let the wrapper function throw a meaningful error in each failure case - there are many! - which the calling function can then display. I have updated the code in my question to extract information from the server in the case of ok == false. I think now it's only in the case of actual server failure that the same error is rethrown. – Little Brain Jan 07 '19 at 14:47

1 Answers1

4

The way to avoid an unhandled promise rejection, is to handle it:

fetchAPI('/api/v1/rest-auth/password/change/', formData).then(response => {
    console.log('response ', response);
}).catch(error => {
   // do something meaningful here.
});;
Jamiec
  • 133,658
  • 13
  • 134
  • 193