Fetch: POST json data

2019-01-01 01:57发布

I'm trying to POST a JSON object using fetch.

From what I can understand, I need to attach a stringified object to the body of the request, e.g.:

fetch("/echo/json/",
{
    headers: {
      'Accept': 'application/json',
      'Content-Type': 'application/json'
    },
    method: "POST",
    body: JSON.stringify({a: 1, b: 2})
})
.then(function(res){ console.log(res) })
.catch(function(res){ console.log(res) })

When using jsfiddle's json echo I'd expect to see the object I've sent ({a: 1, b: 2}) back, but this does not happen - chrome devtools doesn't even show the JSON as part of the request, which means that it's not being sent.

11条回答
墨雨无痕
2楼-- · 2019-01-01 02:15

I have created a thin wrapper around fetch() with many improvements if you are using a purely json REST API:

// Small library to improve on fetch() usage
const api = function(method, url, data, headers = {}){
  return fetch(url, {
    method: method.toUpperCase(),
    body: JSON.stringify(data),  // send it as stringified json
    credentials: api.credentials,  // to keep the session on the request
    headers: Object.assign({}, api.headers, headers)  // extend the headers
  }).then(res => res.ok ? res.json() : Promise.reject(res));
};

// Defaults that can be globally overwritten
api.credentials = 'include';
api.headers = {
  'csrf-token': window.csrf || '',    // only if globally set, otherwise ignored
  'Accept': 'application/json',       // receive json
  'Content-Type': 'application/json'  // send json
};

// Convenient methods
['get', 'post', 'put', 'delete'].forEach(method => {
  api[method] = api.bind(null, method);
});

To use it you have the variable api and 4 methods:

api.get('/todo').then(all => { /* ... */ });

And within an async function:

const all = await api.get('/todo');
// ...

Example with jQuery:

$('.like').on('click', async e => {
  const id = 123;  // Get it however it is better suited

  await api.put(`/like/${id}`, { like: true });

  // Whatever:
  $(e.target).addClass('active dislike').removeClass('like');
});
查看更多
人气声优
3楼-- · 2019-01-01 02:20

After spending some times, reverse engineering jsFiddle, trying to generate payload - there is an effect.

Please take eye (care) on line return response.json(); where response is not a response - it is promise.

var json = {
    json: JSON.stringify({
        a: 1,
        b: 2
    }),
    delay: 3
};

fetch('/echo/json/', {
    method: 'post',
    headers: {
        'Accept': 'application/json, text/plain, */*',
        'Content-Type': 'application/json'
    },
    body: 'json=' + encodeURIComponent(JSON.stringify(json.json)) + '&delay=' + json.delay
})
.then(function (response) {
    return response.json();
})
.then(function (result) {
    alert(result);
})
.catch (function (error) {
    console.log('Request failed', error);
});

jsFiddle: http://jsfiddle.net/egxt6cpz/46/ && Firefox > 39 && Chrome > 42

查看更多
临风纵饮
4楼-- · 2019-01-01 02:21

With ES2017 async/await support, this is how to POST a JSON payload:

(async () => {
  const rawResponse = await fetch('https://httpbin.org/post', {
    method: 'POST',
    headers: {
      'Accept': 'application/json',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({a: 1, b: 'Textual content'})
  });
  const content = await rawResponse.json();

  console.log(content);
})();

Can't use ES2017? See @vp_art's answer using promises

The question however is asking for an issue caused by a long since fixed chrome bug.
Original answer follows.

chrome devtools doesn't even show the JSON as part of the request

This is the real issue here, and it's a bug with chrome devtools, fixed in Chrome 46.

That code works fine - it is POSTing the JSON correctly, it just cannot be seen.

I'd expect to see the object I've sent back

that's not working because that is not the correct format for JSfiddle's echo.

The correct code is:

var payload = {
    a: 1,
    b: 2
};

var data = new FormData();
data.append( "json", JSON.stringify( payload ) );

fetch("/echo/json/",
{
    method: "POST",
    body: data
})
.then(function(res){ return res.json(); })
.then(function(data){ alert( JSON.stringify( data ) ) })

For endpoints accepting JSON payloads, the original code is correct

查看更多
何处买醉
5楼-- · 2019-01-01 02:21

From search engines, I ended up on this topic for non-json posting data with fetch, so thought I would add this.

For non-json you don't have to use form data. You can simply set the Content-Type header to application/x-www-form-urlencoded and use a string:

fetch('url here', {
    method: 'POST',
    headers: {'Content-Type':'application/x-www-form-urlencoded'}, // this line is important, if this content-type is not set it wont work
    body: 'foo=bar&blah=1'
});

An alternative way to build that body string, rather then typing it out as I did above, is to use libraries. For instance the stringify function from query-string or qs packages. So using this it would look like:

import queryString from 'query-string';
fetch('url here', {
    method: 'POST',
    headers: {'Content-Type':'application/x-www-form-urlencoded'}, // this line is important, if this content-type is not set it wont work
    body: queryString.stringify({for:'bar', blah:1}
});
查看更多
泪湿衣
6楼-- · 2019-01-01 02:24

This is related to Content-Type. As you might have noticed from other discussions and answers to this question some people were able to solve it by setting Content-Type: 'application/json'. Unfortunately in my case it didn't work, my POST request was still empty on the server side.

However, if you try with jQuery's $.post() and it's working, the reason is probably because of jQuery using Content-Type: 'x-www-form-urlencoded' instead of application/json.

data = Object.keys(data).map(key => encodeURIComponent(key) + '=' + encodeURIComponent(data[key])).join('&')
fetch('/api/', {
    method: 'post', 
    credentials: "include", 
    body: data, 
    headers: {'Content-Type': 'application/x-www-form-urlencoded'}
})
查看更多
登录 后发表回答