Fetch: POST ข้อมูล json


562

ฉันพยายามที่จะโพสต์วัตถุ JSON ใช้ดึงข้อมูล

จากสิ่งที่ฉันสามารถเข้าใจได้ฉันต้องแนบวัตถุที่เป็นสตริงกับส่วนของคำขอเช่น:

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) })

เมื่อใช้json echo ของ jsfiddleฉันคาดว่าจะเห็นวัตถุที่ฉันส่ง ( {a: 1, b: 2}) กลับ แต่สิ่งนี้ไม่ได้เกิดขึ้น - chrome devtools ไม่แสดง JSON เป็นส่วนหนึ่งของคำขอซึ่งหมายความว่ามันไม่ได้ถูกส่ง


สิ่งที่เบราว์เซอร์ที่คุณใช้?
Krzysztof Safjanowski

@KrzysztofSafjanowski chrome 42 ซึ่งหมายถึงการรับการสนับสนุนอย่างเต็มรูปแบบ
Razor

ตรวจสอบซอนี้jsfiddle.net/abbpbah4/2 คุณคาดหวังข้อมูลอะไรอยู่? เพราะรับคำขอของfiddle.jshell.net/echo/jsonกำลังแสดงวัตถุว่างเปล่า {}
Kaushik

@KaushikKishore แก้ไขเพื่อชี้แจงผลลัพธ์ที่คาดหวัง ควรกลับres.json() {a: 1, b: 2}
มีดโกน

1
คุณลืมที่จะรวมjsonคุณสมบัติที่มีข้อมูลที่คุณต้องการส่ง อย่างไรก็ตามฉันbodyไม่ได้รับการปฏิบัติอย่างถูกต้องอยู่แล้ว ดูซอนี้เพื่อดูว่าการหน่วงเวลา 5 วินาทีถูกข้ามไป jsfiddle.net/99arsnkgนอกจากนี้เมื่อคุณพยายามที่จะเพิ่มส่วนหัวเพิ่มเติมพวกเขาจะถูกละเว้น นี่อาจเป็นปัญหากับfetch()ตัวเอง
boombox

คำตอบ:


598

ด้วยasync/awaitการสนับสนุน ES2017 นี่คือวิธีPOSTการบรรจุ JSON:

(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);
})();

ใช้ ES2017 ไม่ได้หรือ ดูคำตอบของ @ vp_art โดยใช้สัญญา

อย่างไรก็ตามคำถามกำลังถามปัญหาที่เกิดจากข้อผิดพลาดของโครเมี่ยมคงที่มานาน
คำตอบเดิมมีดังนี้

chrome devtools ไม่ได้แสดง JSON เป็นส่วนหนึ่งของคำขอ

นี่เป็นปัญหาจริงที่นี่และเป็นปัญหากับ Chrome devtoolsซึ่งแก้ไขใน Chrome 46

รหัสนั้นใช้งานได้ดี - มันกำลังโพสต์ JSON อย่างถูกต้องมันแค่มองไม่เห็น

ฉันคาดว่าจะเห็นสิ่งที่ฉันส่งกลับมา

ที่ไม่ได้ทำงานเพราะที่ไม่ได้เป็นรูปแบบที่ถูกต้องสำหรับเสียงสะท้อนของ JSfiddle

รหัสที่ถูกต้องคือ

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 ) ) })

สำหรับจุดปลายที่รับ payloads ของ JSON รหัสต้นฉบับนั้นถูกต้อง


15
สำหรับบันทึกนี้ไม่ได้โพสต์บรรจุ JSON - นี่คือการโพสต์รูปแบบ ( x-www-form-urlencoded) กับ JSON jsonข้อมูลในเขตที่มีชื่อ ดังนั้นข้อมูลจะถูกเข้ารหัสเป็นสองเท่า สำหรับโพสต์ JSON ที่สะอาดดูคำตอบโดย @vp_arth ด้านล่าง
mindplay.dk

1
@ mindplay.dk นี่ไม่ใช่ x-www-form-urlencoded post Fetch API ใช้การเข้ารหัสหลายส่วน / แบบฟอร์มข้อมูลบนวัตถุ FormData
JukkaP

@JukkaP ฉันยืนแก้ไข ประเด็นหลักของฉันคือปัญหาการเข้ารหัสสองครั้ง
mindplay.dk

2
ประเภทเนื้อหายังคงเป็นข้อความ / html; charset = iso-8859-1 ไม่รู้ว่าฉันทำอะไรผิด ...
KT Works

3
ถ้าจะให้ปลอดภัยควรทำการยืนยันres.okในกรณีที่รหัสตอบกลับเป็นข้อผิดพลาดบางอย่าง มันจะเป็นการดีถ้ามี.catch()ประโยคในตอนท้าย ฉันรู้ว่านี่เป็นเพียงตัวอย่างตัวอย่าง แต่จำสิ่งเหล่านี้ไว้ในใจสำหรับการใช้งานจริง
Ken Lyon

206

ฉันคิดว่าปัญหาของคุณjsfiddleสามารถดำเนินการform-urlencodedตามคำขอเท่านั้น

แต่วิธีที่ถูกต้องในการขอ json นั้นถูกต้องjsonตามเนื้อความ:

fetch('https://httpbin.org/post', {
  method: 'post',
  headers: {
    'Accept': 'application/json, text/plain, */*',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({a: 7, str: 'Some string: &=&'})
}).then(res=>res.json())
  .then(res => console.log(res));


6
นี่เป็นคำตอบที่ถูกต้อง, ช่วงเวลา - ทุกคนดูเหมือนจะสับสนกับx-www-form-urlencodedvs application/json, ไม่ตรงกันพวกเขาหรือการห่อ JSON สองครั้งในสตริงที่เข้ารหัส url
mindplay.dk

แต่วิธีนี้ใช้ไม่ได้กับ jsfiddle ดังนั้นฉันไม่แน่ใจว่าฉันเข้าใจว่าทำไมคุณถึงพูดว่า "นี่คือทางออกที่ถูกต้องระยะเวลา" ไม่มีใครทำห่อหุ้มเพื่อให้เป็นไปตาม API ของ/echoเส้นทางของ jsfiddle หรือ
adam-beck

69

จากเครื่องมือค้นหาฉันลงเอยในหัวข้อนี้สำหรับการโพสต์ข้อมูลที่ไม่ใช่ json ด้วยการดึงข้อมูลดังนั้นคิดว่าฉันจะเพิ่มสิ่งนี้

สำหรับผู้ที่ไม่ใช่ jsonคุณไม่จำเป็นต้องใช้ข้อมูลฟอร์ม คุณสามารถตั้งค่าContent-Typeส่วนหัวเป็นapplication/x-www-form-urlencodedและใช้สตริง:

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'
});

อีกทางเลือกหนึ่งในการสร้างbodyสตริงนั้นแทนที่จะพิมพ์ตามที่ฉันได้ทำไว้ข้างต้นคือใช้ไลบรารี เช่นstringifyฟังก์ชั่นจากquery-stringหรือqsแพคเกจ ดังนั้นเมื่อใช้สิ่งนี้จะมีลักษณะดังนี้:

import queryString from 'query-string'; // import the queryString class

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}) //use the stringify object of the queryString class
});

2
ขอบคุณมากสำหรับสตริงข้อความค้นหาฉันพยายามหลายครั้งด้วย JSON.stringify แต่ Ajax ไม่ตอบกลับ แต่สตริงการสืบค้นทำเคล็ดลับ ฉันก็พบว่ามันเป็นเพราะดึงสร้าง json สำหรับ params ร่างกายแทนการสร้างสตริง
ภาษาเดนมาร์ก

1
ขอบคุณคน! นี่คือคำตอบที่ดีที่สุด! ฉันกดปุ่มกำแพงเมื่อวานไม่กี่ชั่วโมงพยายามหาวิธีส่ง 'เนื้อหา' ด้วยข้อมูลฟอร์มจากเว็บแอปพลิเคชันของฉันไปยังเซิร์ฟเวอร์ ... หนึ่งคำแนะนำ: $ npm ติดตั้ง cors - ประหยัดนี่เป็นสิ่งจำเป็นเพื่อกำจัด " โหมด: 'no-cors' "ในคำขอการดึงข้อมูลดูgithub.com/expressjs/cors
Alexander Cherednichenko

ขอบคุณ @AlexanderCherednichenko! และขอบคุณสำหรับการแบ่งปัน cors ที่ทราบว่าเป็นสิ่งที่น่าสนใจที่ฉันไม่รู้ :)
Noitidart

1
ขอบคุณจากส่วนลึกของหัวใจ คุณช่วยประหยัดเวลาของฉันและช่วยชีวิตฉันได้สองครั้ง :)
23490

1
Thnaks @bafsar!
Noitidart

42

หลังจากใช้เวลาไปแล้ววิศวกรรมย้อนกลับ jsFiddle กำลังพยายามสร้างน้ำหนักบรรทุก - มีผลกระทบ

โปรดใช้สายตา (ระวัง) ในบรรทัดreturn response.json();ที่การตอบสนองไม่ใช่คำตอบ - มันเป็นสัญญา

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


ทำไมถึง'x-www-form-urlencodedเป็นเช่นนั้นapplication/json? ความแตกต่างคืออะไร?
Juan Picado

@JuanPicado - หลังจากวิศวกรรมย้อนกลับของjsfiddle เมื่อ 2 ปีก่อนมันเป็นเพียงทางเลือกเดียวที่สามารถใช้งานได้ แน่นอนว่าapplication/jsonเป็นรูปแบบที่ถูกต้องและใช้งานได้ในขณะนี้ ขอบคุณสำหรับสายตาที่ดี
:)

YW รายละเอียดอยากรู้อยากเห็นการทำงานสำหรับฉันในวิธีการแบบเก่าด้วยfetch( stackoverflow.com/questions/41984893/... ) application/jsonแทน บางทีคุณอาจรู้ว่าทำไม ...
Juan Picado

6
Content-Typeเป็นapplication/jsonแต่ที่แท้จริงของคุณbodyจะปรากฏขึ้นที่จะเป็นx-www-form-urlencoded- ฉันไม่คิดว่าเรื่องนี้ควรจะทำงาน? หากใช้งานได้เซิร์ฟเวอร์ของคุณจะต้องให้อภัย คำตอบโดย @vp_arth ด้านล่างดูเหมือนจะเป็นคำตอบที่ถูกต้อง
mindplay.dk

18

ฉันได้สร้าง wrapper เล็ก ๆ รอบ fetch () พร้อมการปรับปรุงมากมายหากคุณใช้ 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);
});

หากต้องการใช้คุณมีตัวแปรapiและ 4 วิธี:

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

และภายในasyncฟังก์ชั่น:

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

ตัวอย่างด้วย 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');
});

ฉันคิดว่าคุณหมายถึงชุดของการขัดแย้งที่แตกต่างกันไปObject.assign? ควรเป็นObject.assign({}, api.headers, headers)เพราะคุณไม่ต้องการเพิ่มการกำหนดเองต่อไปheadersapi.headersลงในกัญชาที่พบบ่อย ขวา?
Mobigital

@ Digital ทั้งหมดฉันไม่ทราบเกี่ยวกับความแตกต่างนั้น แต่ตอนนี้มันเป็นวิธีเดียวที่ฉันทำ
Francisco Presencia

11

Content-Typeนี้จะเกี่ยวข้องกับ ดังที่คุณอาจสังเกตเห็นจากการสนทนาและคำตอบของคำถามนี้บางคนสามารถแก้ไขได้โดยการตั้งค่าContent-Type: 'application/json'คำถามนี้บางคนมีความสามารถที่จะแก้ปัญหาได้โดยการตั้งค่า ขออภัยในกรณีของฉันมันไม่ทำงานคำขอ POST ของฉันยังคงว่างเปล่าที่ฝั่งเซิร์ฟเวอร์

แต่ถ้าคุณลองกับของ jQuery $.post()และก็ทำงานเหตุผลคืออาจจะเป็นเพราะ jQuery ใช้แทนContent-Type: 'x-www-form-urlencoded'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'}
})

1
ผู้พัฒนาแบ็กเอนด์ของฉันสร้าง API ออกมาพร้อมกับ PHP ฉันคาดหวังว่าข้อมูลจะเป็นสตริงข้อความค้นหาไม่ใช่วัตถุ json นี่เป็นการแก้ไขการตอบสนองที่ว่างเปล่าในฝั่งเซิร์ฟเวอร์
eballeste

11

มีปัญหาเดียวกัน - ไม่ bodyการส่งจากลูกค้าไปยังเซิร์ฟเวอร์

การเพิ่มContent-Typeส่วนหัวแก้ไขให้ฉันแล้ว:

var headers = new Headers();

headers.append('Accept', 'application/json'); // This one is enough for GET requests
headers.append('Content-Type', 'application/json'); // This one sends body

return fetch('/some/endpoint', {
    method: 'POST',
    mode: 'same-origin',
    credentials: 'include',
    redirect: 'follow',
    headers: headers,
    body: JSON.stringify({
        name: 'John',
        surname: 'Doe'
    }),
}).then(resp => {
    ...
}).catch(err => {
   ...
})

7

คำตอบยอดนิยมใช้ไม่ได้กับ PHP7 เพราะมันมีการเข้ารหัสผิด แต่ฉันสามารถหาการเข้ารหัสที่ถูกต้องกับคำตอบอื่น ๆ ได้ รหัสนี้ยังส่งคุกกี้การตรวจสอบสิทธิ์ซึ่งคุณอาจต้องการเมื่อจัดการกับเช่นฟอรัม PHP:

julia = function(juliacode) {
    fetch('julia.php', {
        method: "POST",
        credentials: "include", // send cookies
        headers: {
            'Accept': 'application/json, text/plain, */*',
            //'Content-Type': 'application/json'
            "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8" // otherwise $_POST is empty
        },
        body: "juliacode=" + encodeURIComponent(juliacode)
    })
    .then(function(response) {
        return response.json(); // .text();
    })
    .then(function(myJson) {
        console.log(myJson);
    });
}

3

อาจเป็นประโยชน์กับบางคน:

ฉันพบปัญหาที่ formdata ไม่ได้ถูกส่งสำหรับคำขอของฉัน

ในกรณีของฉันมันเป็นการรวมกันของส่วนหัวต่อไปนี้ซึ่งเป็นสาเหตุของปัญหาและประเภทเนื้อหาที่ไม่ถูกต้อง

ดังนั้นฉันจึงส่งคำขอส่วนหัวสองรายการนี้และไม่ได้ส่ง formdata เมื่อฉันลบส่วนหัวที่ทำงานได้

"X-Prototype-Version" : "1.6.1",
"X-Requested-With" : "XMLHttpRequest"

เช่นเดียวกับคำตอบอื่น ๆ แนะนำว่าส่วนหัวของประเภทเนื้อหาจะต้องถูกต้อง

สำหรับคำขอของฉันส่วนหัวประเภทเนื้อหาที่ถูกต้องคือ:

"Content-Type": "application / x-www-form-urlencoded; charset = UTF-8"

ดังนั้นบรรทัดล่างถ้า formdata ของคุณไม่ได้ถูกแนบมากับการร้องขอมันอาจเป็นส่วนหัวของคุณ ลองนำส่วนหัวของคุณให้น้อยที่สุดแล้วลองเพิ่มทีละส่วนเพื่อดูว่าปัญหาของคุณได้รับการแก้ไขหรือไม่


3

ฉันคิดว่าเราไม่จำเป็นต้องแยกวิเคราะห์วัตถุ JSON เป็นสตริงถ้าเซิร์ฟเวอร์ระยะไกลยอมรับ json พวกเขาขอเพียงแค่เรียกใช้:

const request = await fetch ('/echo/json', {
  headers: {
    'Content-type': 'application/json'
  },
  method: 'POST',
  body: { a: 1, b: 2 }
});

เช่นการขอขด

curl -v -X POST -H 'Content-Type: application/json' -d '@data.json' '/echo/json'

ในกรณีที่รีโมตเซอร์วิสไม่ยอมรับไฟล์ json เป็น body เพียงส่ง dataForm:

const data =  new FormData ();
data.append ('a', 1);
data.append ('b', 2);

const request = await fetch ('/echo/form', {
  headers: {
    'Content-type': 'application/x-www-form-urlencoded'
  },
  method: 'POST',
  body: data
});

เช่นการขอขด

curl -v -X POST -H 'Content-type: application/x-www-form-urlencoded' -d '@data.txt' '/echo/form'

2
สิ่งนี้ไม่ถูกต้องโจ่งแจ้ง มันไม่มีส่วนเกี่ยวข้องกับฝั่งเซิร์ฟเวอร์ไม่ว่าคุณจะต้องทำให้ json ของคุณเป็นสตริงหรือไม่ นั่นคือสิ่งที่curlคำสั่งของคุณทำโดยปริยาย! ถ้าคุณไม่ทำให้วัตถุของคุณเป็นวัตถุก่อนที่จะผ่านมันไปเพราะbodyคุณจะส่ง"[object Object]"ไปตามคำขอของคุณ การทดสอบอย่างง่ายในเครื่องมือ Dev จะแสดงให้คุณเห็นว่า เปิดขึ้นแล้วลองทำโดยไม่ต้องออกจากแท็บนี้:a = new FormData(); a.append("foo","bar"); fetch("/foo/bar", { method: 'POST', body: {}, headers: { 'Content-type': 'application/json' } })
oligofren

2

หากส่วนของข้อมูล JSON ของคุณมีอาร์เรย์และวัตถุที่ซ้อนกันฉันจะใช้URLSearchParams และparam()วิธีการของ jQuery

fetch('/somewhere', {
  method: 'POST',
  body: new URLSearchParams($.param(payload))
})

สำหรับเซิร์ฟเวอร์ของคุณนี่จะดูเหมือน HTML มาตรฐานที่<form>กำลังPOSTแก้ไข

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.