Làm cách nào để tôi tải lên một tệp với API tìm nạp JS?


170

Tôi vẫn đang cố quấn đầu quanh nó.

Tôi có thể yêu cầu người dùng chọn tệp (hoặc thậm chí nhiều) với đầu vào tệp:

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

Và tôi có thể bắt submitsự kiện bằng cách sử dụng <fill in your event handler here>. Nhưng một khi tôi làm, làm thế nào để tôi gửi các tập tin bằng cách sử dụng fetch?

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

1
tài liệu chính thức hoạt động với tôi sau khi thử một số câu trả lời không thành công: developer.mozilla.org/en-US/docs/Web/API/Fetch_API/ , một cái gì đó có thể xác nhận: 1. cần tệp bọc trong FromData; 2. không cần khai báo Content-Type: multipart/form-datatrong tiêu đề yêu cầu
Spark.Bao

Câu trả lời:


127

Đây là một ví dụ cơ bản với ý kiến. Các uploadchức năng là những gì bạn đang tìm kiếm:

// 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
Tại sao ví dụ này bao gồm các tiêu đề Kiểu Nội dung nhưng một câu trả lời khác lại bỏ qua chúng khi gửi tệp bằng Fetch API? Đó là cái nào
jjrabbit

12
KHÔNG đặt Loại nội dung. Tôi đã dành rất nhiều thời gian để cố gắng làm cho nó hoạt động và sau đó tìm thấy bài viết này nói rằng không đặt nó. Và nó hoạt động! muffinman.io/uploading-files-USE-fetch-multipart-form-data
Kostiantyn

Làm thế nào bạn sẽ đọc tệp này từ hãy nói Express backend. Vì tệp không được gửi dưới dạng dữ liệu. Nó được gửi thay vì chỉ là đối tượng tập tin. Liệu express-fileupload hoặc multer phân tích các tải trọng như vậy?
sakib11

221

Tôi đã làm nó như thế này:

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
Bạn không cần phải bọc nội dung tệp trong một FormDatađối tượng nếu tất cả những gì bạn đang tải lên là tệp (đó là những gì câu hỏi ban đầu muốn). fetchsẽ chấp nhận input.files[0]ở trên là bodytham số của nó .
Klaus

17
Nếu bạn có một phụ trợ PHP xử lý việc tải lên tệp, bạn sẽ muốn bọc tệp trong FormData để mảng $ _FILES được điền đúng.
ddelrio1986

2
Tôi cũng nhận thấy rằng Google Chrome sẽ không hiển thị tệp trong tải trọng yêu cầu mà không có phần FormData vì một số lý do. Có vẻ như một lỗi trong bảng điều khiển Mạng của Google Chrome.
ddelrio1986

4
Đây thực sự nên là câu trả lời chính xác. Một cách khác cũng hoạt động nhưng phức tạp hơn
jnmandal

bạn có ý nghĩa gì bởi / avatar? Bạn đang đề cập đến một số điểm cuối API phụ trợ?
Kartikeya Mishra

90

Một lưu ý quan trọng để gửi tệp bằng Fetch API

Người ta cần bỏ qua content-typetiêu đề cho yêu cầu Tìm nạp. Sau đó, trình duyệt sẽ tự động thêm Content typetiêu đề bao gồm cả Ranh giới biểu mẫu trông giống như

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

Ranh giới biểu mẫu là dấu phân cách cho dữ liệu biểu mẫu


17
ĐIỀU NÀY! Rất quan trọng! Không sử dụng loại nội dung của riêng bạn với tìm nạp trên nhiều phần. Tôi không biết tại sao mã của tôi không hoạt động.
Ernestas Stankevičius


1
Đây là vàng! Tôi đã lãng phí 1 giờ không hiểu điều này. Cảm ơn đã chia sẻ mẹo này
Ashwin Mitchhu

1
Downvote vì mặc dù đó là thông tin hữu ích, nhưng điều này không cố gắng trả lời câu hỏi của OP.
toraritte

3
Đây là thông tin rất quan trọng không được ghi lại trong các tài liệu Tìm nạp MDN .
Plasty Grove

36

Nếu bạn muốn nhiều tập tin, bạn có thể sử dụng

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 Tôi gặp vấn đề tương tự, đó là do hàm API của tôi đang cố gắng chuyển đổi formData thành JSON. (Tôi chỉ nhận xét về cơ hội tắt mà nó giúp được ai đó)
mp035

19

Để gửi một tập tin duy nhất, bạn chỉ có thể sử dụng các Fileđối tượng từ input's .filesmảng trực tiếp như giá trị của body:trong bạn fetch()khởi tạo:

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],
});

Điều này hoạt động vì Filekế thừa từ BlobBloblà một trong những BodyInitloại được cho phép được xác định trong Tiêu chuẩn tìm nạp.


Đây là câu trả lời đơn giản nhất nhưng làm thế nào body: myInput.files[0]gây ra số lượng byte được giữ trong bộ nhớ ở phía máy khách?
bhantol

2
Tôi hy vọng rằng với giải pháp này, trình duyệt sẽ đủ nhạy cảm để truyền tệp và không yêu cầu phải đọc nó vào bộ nhớ, @bhantol, nhưng tôi đã không tìm ra cách nào để tìm hiểu (theo kinh nghiệm hoặc bằng cách đào sâu vào thông số kỹ thuật). Nếu bạn muốn xác nhận, bạn có thể thử (trong mỗi trình duyệt chính) bằng cách sử dụng phương pháp này để tải lên tệp 50 GB hoặc một cái gì đó, và xem liệu trình duyệt của bạn có cố sử dụng quá nhiều bộ nhớ và bị giết không.
Đánh dấu Amery

Không làm việc cho tôi. express-fileuploadthất bại trong việc phân tích luồng yêu cầu. Nhưng FormDatahoạt động như một lá bùa.
attacomsian

1
@attacomsian Nhìn thoáng qua, có vẻ như tôi express-fileuploadlà một thư viện máy chủ để xử lý multipart/form-datacác yêu cầu có chứa các tệp, vì vậy, nó không tương thích với phương pháp này (chỉ gửi trực tiếp tệp dưới dạng thân yêu cầu).
Đánh dấu Amery

6

Câu trả lời được chấp nhận ở đây là một chút ngày. Kể từ tháng 4 năm 2020, một cách tiếp cận được đề xuất trên trang web MDN cho thấy việc sử dụng FormDatavà cũng không yêu cầu đặt loại nội dung. https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch

Tôi đang trích dẫn đoạn mã để thuận tiện:

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
Sử dụng FormDatasẽ chỉ hoạt động nếu máy chủ đang mong đợi dữ liệu biểu mẫu. Nếu máy chủ muốn có một tệp thô là phần thân của POST thì câu trả lời được chấp nhận là chính xác.
Clyde

2

Nhảy ra từ cách tiếp cận của Alex Montoya cho nhiều yếu tố đầu vào tệp

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

Vấn đề đối với tôi là tôi đã sử dụng một answer.blob () để điền dữ liệu biểu mẫu. Rõ ràng bạn không thể làm điều đó ít nhất là với phản ứng bản địa nên cuối cùng tôi đã sử dụng

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

Fetch dường như nhận ra định dạng đó và gửi tệp nơi uri đang trỏ.


0

Đây là mã của tôi:

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
Từ đánh giá: Xin chào, xin vui lòng không trả lời chỉ với mã nguồn. Cố gắng cung cấp một mô tả hay về cách giải pháp của bạn hoạt động. Xem: Làm thế nào để tôi viết một câu trả lời tốt? . Cảm ơn
sunıɔ qɐp
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.