Fetch api - getting json body in both then and cat

2019-07-29 05:26发布

问题:

I am using fetch api for fetching an URL that might return:

Response : status = 200, json body = {'user': 'abc', 'id': 1}

or

Response : status = 400 , json body = {'reason': 'some reason'}

or

Response : status = 400 , json body = {'reason': 'some other reason'}

I want to make a separate function request() that I use from various parts of my code as follows:

    request('http://api.example.com/').then(
        // status 200 comes here
        data => // do something with data.id, data.user
    ).catch(
        // status 400, 500 comes here
        error =>  // here error.reason will give me further info, i also want to know whether status was 400 or 500 etc
    )

I am unable to do the split between 200 and 400,500 (i have tried by throwing an error). When I throw an error, I am finding it hard to still extract the JSON body (to use for error.reason).

My current code is as follows:

import 'whatwg-fetch';

/**
 * Requests a URL, returning a promise
 */
export default function request(url, options={}) {

    console.log('sending api request, url = ' + url)

    return fetch(url, options)
        .then(checkStatus)
        .then(parseJSON)
        .then((data) => ({data}))
        .catch((err) => ({err}));
}


function checkStatus(response) {
    if (response.status >= 200 && response.status < 300) {
        return response;
    }

    const error = new Error(response.statusText);
    error.response = response;
    throw error;
}



function parseJSON(response) {
    return response.json();   // json() is a promise itself
}

I have tried to solve this by doing as follows, by inverting the order of .then() calls, but does not work

export default function request(url, options) {
    return fetch(url, options)
        .then(parseJSON)        // note that now first calling parseJSON to get not just JSON but also status. 
        .then(checkStatus)      // i.e. Inverted order of the two functions from before

        .then((data) => ({data}))
        .catch((err) => ({err}));
}

function checkStatus({data, status}) {

    if (status >= 200 && status < 300) {
        return data;
    }
    else {
        // const error = new Error(response.statusText);
        const error = new Error("Something went wrong");
        // error.response = response;
        error.data = data;

        throw error;
    }

}

function parseJSON(response) {
    let jsonBody

    response.json().then(json => {
        jsonBody = json                 // this does not help, i thought it will make jsonBody fill up, but seems its in a diff thread
    })              

    return {
        data: jsonBody,
        status: response.status     // my aim is to send a whole dict with status and data to send it to checkStatus, but this does not work
    }
}

回答1:

response.json() returns an asynchronous result. You are not returning the object at parseJSON from within .then() chained to response.json(). To correct that issue you can return response.json() promise at parseJSON call and return object containing data and status from within .then() chained to response.json()

function parseJSON(response) {
    return response.json().then(json => {
          return {
                   data: json,
                   status: response.status  
                 }
    })         
}  


回答2:

Here's slightly different approach: With a one-liner I create a response-like promise with ok, status and json-as-object (not a promise), then I decide what to do with this object. Generally I reject with response if response.ok is false, otherwise I resolve with only the json-data. Network errors/json-parse-errors are rejected as usual.

fetch(url, options)
    .then(r => r.json().then(json => ({ok: r.ok, status: r.status, json})))
    .then( r => r.ok ? r.json: Promise.reject(r))