grazdev
grazdev

Reputation: 1272

How to deal with Node error 'Unhandled promise rejection' so that page gets loaded anyway?

I'm creating a Next.js app served by a Node (Express) server which pulls in data through requests to an API. I keep my request endpoints in a separate api file:

const apiBase = 'http://www.example.com'

export default {
    news: apiBase + '/news/'
    // other endpoints
}

Then I do my requests in getInitialProps, and do conditional rendering based on whether the request gives an error or not:

static async getInitialProps( { query: { slug } } ) {
    const news = await asyncReq( api.news + slug )
    return { news, error: news.status }
}

render() {
    return this.props.error ? <Error /> : <News />
}

asyncReq is a helper function that looks like this:

export const asyncReq = endpoint => {
    return 
        fetch( endpoint )
        .then( res => { return res.ok ? res.json() : res } )
}

This all works fine both when the request is successful and when I get 404 or 500 errors. But suppose I intentionally use a wrong endpoint:

const apiBase = 'http://www.example.com'

export default {
    news: wrongApiBase + '/news/'
    // other endpoints
}

In this case, Node gives me the following error because wrongApiBase is undefined:

UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 498)

which is what it should do, but the error causes the page to never get loaded. What am I supposed to do to handle the error? My idea was to chain a catch statement to asyncReq, but I'm not sure what I should return from it that I can then use in getInitialProps. I tried returning false but nothing changes, the page just doesn't load.

export const asyncReq = endpoint => {
    return 
        fetch( endpoint )
        .then( res => { return res.ok ? res.json() : res } )
        .catch( err => { // What should I return here? )
}

+++ UPDATE +++

As it turns out, there was an issue with the error I was producing. Like I said, I was using a wrong variable name (wrongBaseApi) to trigger an error, which caused Node to never serve the page. In hindsight, it makes sense, as it's an error with Node code and not with the incoming request.

By using the right variable but assigning it a wrong value (an actually wrong API base, such as http://dahfjkdahfds.com, which is a not a Node error but an error with the request), I was able to make the solution of using a try/catch block offered by @iKoala and @DEVCNN work. So my code became:

static async getInitialProps( { query: { slug } } ) {
    const news = await asyncReq( api.news + slug )
    return { news }
}

render() {
    // this.props.news.data
    // this.props.news.error
}

and

export async function asyncReq( endpoint ) {
    try {
        const res = await fetch( endpoint )
        return { 
            data: res.ok ? await res.json().then( val => { return val } ) : null,
            error: res.ok ? false : res.status
        } 
    } catch( error ) {
        return { error } 
    }
}

Upvotes: 1

Views: 4066

Answers (4)

DEVCNN
DEVCNN

Reputation: 622

Let's say your getInitialProps calls asyncReq which in turn calls throwError method. Now if throwError method throws an error, you can catch it with the catch block in getInitialProps. So you should always put a catch block in the function that starts the function chain. To diagnose errors better, you should put the catch block in each function. But as a general rule, catch block in the first function that you call is a must.

getInitialProps = async function( { query: { slug } } ) {
try{
const news = await asyncReq( 'http://localhost:3000/' + slug )
return { news, error: news.status }
}
catch(err){
console.log('err:', err);
}
}

const throwError = () => {
throw 'New Error'
}

const asyncReq = endpoint => {
throwError();
return 
    fetch( endpoint )
    .then( res => { return res.ok ? res.json() : res } )
}

Upvotes: 0

NullDev
NullDev

Reputation: 7303

Since it's NodeJS, I would use process.on() (instead of window.addEventListener()) with the unhandledRejection event:

process.on("unhandledRejection", (err, promise) => {
    console.log(`Unhandled rejection (promise: ${promise}, reason: ${err})`);
});

Upvotes: 0

Mr.Throg
Mr.Throg

Reputation: 1005

Not a good approach but we can achieve by adding an event listener on the root node

 window.addEventListener('unhandledrejection', rj => {
      this.setState({hasError: true})
    })

something like this.

Upvotes: 0

iKoala
iKoala

Reputation: 880

I think you have to handle the error thrown from asyncReq:

static async getInitialProps( { query: { slug } } ) {
  try {
    const news = await asyncReq( api.news + slug )
    return { news, error: news.status }
  } catch (err) {
    // any error, including http error 40x and 50x
    return { news: null, error: err };
  }
}

Upvotes: 2

Related Questions