ฉันจะอัพโหลดไฟล์ด้วย JS fetch API ได้อย่างไร


170

ฉันยังคงพยายามห่อหัวของฉันรอบ ๆ มัน

ฉันสามารถให้ผู้ใช้เลือกไฟล์ (หรือหลาย ๆ ไฟล์) ด้วยอินพุตไฟล์:

<form>
  <div>
    <label>Select file to upload</label>
    <input type="file">
  </div>
  <button type="submit">Convert</button>
</form>

และผมสามารถจับเหตุการณ์ใช้submit <fill in your event handler here>แต่เมื่อฉันทำฉันจะส่งไฟล์โดยใช้fetch?

fetch('/files', {
  method: 'post',
  // what goes here? What is the "body" for this? content-type header?
}).then(/* whatever */);

1
เอกสารอย่างเป็นทางการใช้งานได้สำหรับฉันหลังจากที่พยายามคำตอบบางอย่างล้มเหลว: developer.mozilla.org/en-US/docs/Web/API/Fetch_API/…มีบางอย่างที่สามารถยืนยันได้: 1. ต้องใช้ไฟล์ wrap ใน FromData; 2. ไม่จำเป็นต้องประกาศContent-Type: multipart/form-dataในส่วนหัวของคำขอ
Spark.Bao

คำตอบ:


127

นี่เป็นตัวอย่างพื้นฐานพร้อมความคิดเห็น uploadฟังก์ชั่นคือสิ่งที่คุณกำลังมองหา:

// Select your input type file and store it in a variable
const input = document.getElementById('fileinput');

// This will upload the file after having read it
const upload = (file) => {
  fetch('http://www.example.net', { // Your POST endpoint
    method: 'POST',
    headers: {
      // Content-Type may need to be completely **omitted**
      // or you may need something
      "Content-Type": "You will perhaps need to define a content-type here"
    },
    body: file // This is your file object
  }).then(
    response => response.json() // if the response is a JSON object
  ).then(
    success => console.log(success) // Handle the success response object
  ).catch(
    error => console.log(error) // Handle the error response object
  );
};

// Event handler executed when a file is selected
const onSelectFile = () => upload(input.files[0]);

// Add a listener on your input
// It will be triggered when a file will be selected
input.addEventListener('change', onSelectFile, false);

8
เหตุใดตัวอย่างนี้จึงรวมส่วนหัวของประเภทเนื้อหา แต่มีคำตอบอื่นที่บอกว่าไม่ต้องรวมเมื่อส่งไฟล์ด้วย Fetch API อันไหน?
jjrabbit

12
ห้ามตั้งค่าประเภทเนื้อหา ฉันใช้เวลามากในการพยายามทำให้มันใช้งานได้และจากนั้นพบบทความนี้ว่าไม่ได้ตั้งค่า และมันใช้งานได้! muffinman.io/uploading-files-using-fetch-multipart-form-data
Kostiantyn

คุณจะอ่านไฟล์นี้อย่างไรจากโปรแกรม Express backend เนื่องจากไฟล์ไม่ถูกส่งเป็นข้อมูลในแบบฟอร์ม มันถูกส่งแทนเป็นเพียงวัตถุไฟล์ Express-fileupload หรือ multer แยกวิเคราะห์ payloads ดังกล่าวหรือไม่?
sakib11

221

ฉันทำแบบนี้แล้ว:

var input = document.querySelector('input[type="file"]')

var data = new FormData()
data.append('file', input.files[0])
data.append('user', 'hubot')

fetch('/avatars', {
  method: 'POST',
  body: data
})

16
คุณไม่จำเป็นต้องตัดเนื้อหาไฟล์ในFormDataวัตถุหากสิ่งที่คุณกำลังอัปโหลดทั้งหมดเป็นไฟล์ (ซึ่งเป็นสิ่งที่คำถามเดิมต้องการ) fetchจะยอมรับinput.files[0]ข้างต้นเป็นbodyพารามิเตอร์
Klaus

17
หากคุณมีแบ็กเอนด์ PHP จัดการการอัปโหลดไฟล์คุณจะต้องล้อมไฟล์ใน FormData เพื่อให้อาร์เรย์ $ _FILES มีการใส่ข้อมูลอย่างถูกต้อง
ddelrio1986

2
ฉันยังสังเกตเห็นว่า Google Chrome จะไม่แสดงไฟล์ในคำขอน้ำหนักบรรทุกโดยไม่มีส่วน FormData ด้วยเหตุผลบางอย่าง ดูเหมือนว่ามีข้อบกพร่องในแผงเครือข่ายของ Google Chrome
ddelrio1986

4
นี่ควรเป็นคำตอบที่ถูกต้องจริงๆ วิธีอื่น ๆ ใช้งานได้ แต่มีความซับซ้อนมากขึ้น
jnmandal

คุณหมายถึงอะไรโดย / อวตาร? คุณหมายถึงจุดปลาย API ของแบ็กเอนด์บ้างไหม?
Kartikeya Mishra

90

หมายเหตุสำคัญสำหรับการส่งไฟล์ด้วย Fetch API

จำเป็นต้องละเว้น content-typeส่วนหัวสำหรับคำขอการดึงข้อมูล จากนั้นเบราว์เซอร์จะเพิ่มContent typeส่วนหัวโดยอัตโนมัติรวมถึงขอบเขตของฟอร์มซึ่งมีลักษณะดังนี้

Content-Type: multipart/form-data; boundary=—-WebKitFormBoundaryfgtsKTYLsT7PNUVD

ขอบเขตแบบฟอร์มเป็นตัวคั่นสำหรับข้อมูลแบบฟอร์ม


17
นี้! สำคัญมาก! อย่าใช้ประเภทเนื้อหาของคุณเองด้วยการดึงหลายส่วน ฉันไม่รู้ว่าทำไมรหัสของฉันไม่ทำงาน
Ernestas Stankevičius


1
นี่คือทองคำ! ฉันเสียเวลา 1 ชั่วโมงไม่เข้าใจสิ่งนี้ ขอบคุณที่แบ่งปันเคล็ดลับนี้
Ashwin Prabhu

1
Downvote เพราะถึงแม้ว่ามันจะเป็นข้อมูลที่เป็นประโยชน์ แต่ก็ไม่ได้พยายามตอบคำถามของ OP
toraritte

3
ข้อมูลนี้เป็นข้อมูลที่สำคัญมากซึ่งจะไม่ได้บันทึกในMDN Fetch เอกสาร
Plasty Grove

36

หากคุณต้องการไฟล์หลายไฟล์คุณสามารถใช้สิ่งนี้

var input = document.querySelector('input[type="file"]')

var data = new FormData()
for (const file of input.files) {
  data.append('files',file,file.name)
}

fetch('/avatars', {
  method: 'POST',
  body: data
})

@ Saly3301 ฉันมีปัญหาเดียวกันมันเป็นเพราะฟังก์ชั่น API ของฉันพยายามแปลง formData เป็น JSON (ฉันเพียงแค่แสดงความคิดเห็นในโอกาสที่จะช่วยใครบางคน)
mp035

19

หากต้องการส่งไฟล์เดียวคุณก็สามารถใช้Fileวัตถุจากinput's .filesอาร์เรย์โดยตรงเป็นค่าของbody:คุณในfetch()การเริ่มต้น:

const myInput = document.getElementById('my-input');

// Later, perhaps in a form 'submit' handler or the input's 'change' handler:
fetch('https://example.com/some_endpoint', {
  method: 'POST',
  body: myInput.files[0],
});

สิ่งนี้ได้ผลเพราะFileสืบทอดมาจากBlobและBlobเป็นหนึ่งในBodyInitประเภทที่อนุญาตที่กำหนดไว้ในมาตรฐานการดึงข้อมูล


นี่เป็นคำตอบที่ง่ายที่สุด แต่จะbody: myInput.files[0]ทำให้เกิดจำนวนไบต์ที่เก็บไว้ในหน่วยความจำที่ฝั่งไคลเอ็นต์ได้อย่างไร
bhantol

2
ฉันคาดหวังว่าด้วยวิธีนี้เบราว์เซอร์จะมีเหตุผลเพียงพอที่จะสตรีมไฟล์และไม่ต้องการให้อ่านในหน่วยความจำ @bhantol แต่ฉันยังไม่ได้ไปหาวิธี (ทั้งจากสังเกตุ ข้อมูลจำเพาะ) หากคุณต้องการยืนยันคุณสามารถลอง (ในแต่ละเบราว์เซอร์หลัก) โดยใช้วิธีนี้ในการอัปโหลดไฟล์ขนาด 50GB หรือบางอย่างและดูว่าเบราว์เซอร์ของคุณพยายามใช้หน่วยความจำมากเกินไปและถูกฆ่า
Mark Amery

ไม่ได้ผลสำหรับฉัน express-fileuploadไม่สามารถแยกวิเคราะห์สตรีมคำขอ แต่FormDataทำงานเหมือนจับใจ
attacomsian

1
@attacomsian อย่างรวดเร็วดูเหมือนว่าฉันexpress-fileuploadเป็นไลบรารีเซิร์ฟเวอร์สำหรับการจัดการmultipart/form-dataคำขอที่มีไฟล์ดังนั้นใช่มันเข้ากันไม่ได้กับวิธีการนี้ (ซึ่งเพิ่งส่งไฟล์โดยตรงเป็นเนื้อหาคำขอ)
Mark Amery

6

คำตอบที่ยอมรับได้ที่นี่ค่อนข้างเก่า เมื่อวันที่เมษายน 2563 แนวทางที่แนะนำบนเว็บไซต์ MDN แนะนำให้ใช้FormDataและไม่ขอตั้งประเภทเนื้อหา https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch

ฉันอ้างถึงข้อมูลโค้ดเพื่อความสะดวก:

const formData = new FormData();
const fileField = document.querySelector('input[type="file"]');

formData.append('username', 'abc123');
formData.append('avatar', fileField.files[0]);

fetch('https://example.com/profile/avatar', {
  method: 'PUT',
  body: formData
})
.then((response) => response.json())
.then((result) => {
  console.log('Success:', result);
})
.catch((error) => {
  console.error('Error:', error);
});

1
การใช้FormDataจะทำงานได้ก็ต่อเมื่อเซิร์ฟเวอร์คาดว่าจะมีข้อมูลในแบบฟอร์ม หากเซิร์ฟเวอร์ต้องการไฟล์ raw เป็นเนื้อความของ POST คำตอบที่ยอมรับนั้นถูกต้อง
ไคลด์

2

กระโดดออกจากแนวทางของ Alex Montoya สำหรับองค์ประกอบไฟล์หลายรายการ

const inputFiles = document.querySelectorAll('input[type="file"]');
const formData = new FormData();

for (const file of inputFiles) {
    formData.append(file.name, file.files[0]);
}

fetch(url, {
    method: 'POST',
    body: formData })

1

ปัญหาสำหรับฉันคือการที่ฉันใช้ response.blob () เพื่อเติมข้อมูลในแบบฟอร์ม เห็นได้ชัดว่าคุณไม่สามารถทำอย่างน้อยกับปฏิกิริยาพื้นเมืองดังนั้นฉันจึงใช้

data.append('fileData', {
  uri : pickerResponse.uri,
  type: pickerResponse.type,
  name: pickerResponse.fileName
 });

ดึงข้อมูลดูเหมือนจะรับรู้รูปแบบนั้นและส่งไฟล์ที่มีการชี้ uri


0

นี่คือรหัสของฉัน:

HTML:

const upload = (file) => {
    console.log(file);

    

    fetch('http://localhost:8080/files/uploadFile', { 
    method: 'POST',
    // headers: {
    //   //"Content-Disposition": "attachment; name='file'; filename='xml2.txt'",
    //   "Content-Type": "multipart/form-data; boundary=BbC04y " //"multipart/mixed;boundary=gc0p4Jq0M2Yt08jU534c0p" //  ή // multipart/form-data 
    // },
    body: file // This is your file object
  }).then(
    response => response.json() // if the response is a JSON object
  ).then(
    success => console.log(success) // Handle the success response object
  ).catch(
    error => console.log(error) // Handle the error response object
  );

  //cvForm.submit();
};

const onSelectFile = () => upload(uploadCvInput.files[0]);

uploadCvInput.addEventListener('change', onSelectFile, false);
<form id="cv_form" style="display: none;"
										enctype="multipart/form-data">
										<input id="uploadCV" type="file" name="file"/>
										<button type="submit" id="upload_btn">upload</button>
</form>
<ul class="dropdown-menu">
<li class="nav-item"><a class="nav-link" href="#" id="upload">UPLOAD CV</a></li>
<li class="nav-item"><a class="nav-link" href="#" id="download">DOWNLOAD CV</a></li>
</ul>


1
จากการทบทวน: สวัสดีโปรดอย่าตอบเพียงแค่มีซอร์สโค้ด พยายามให้คำอธิบายที่ดีเกี่ยวกับวิธีการแก้ปัญหาของคุณ ดู: ฉันจะเขียนคำตอบที่ดีได้อย่างไร . ขอบคุณ
sɐunıɔןɐqɐp
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.