Jak przesłać plik za pomocą interfejsu JS Fetch API?

170

Wciąż staram się owinąć wokół tego głowę.

Mogę poprosić użytkownika o wybranie pliku (lub nawet wielu) za pomocą wejścia pliku:

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

I mogę złapać submitwydarzenie za pomocą <fill in your event handler here>. Ale kiedy już to zrobię, jak wysłać plik przy użyciu fetch?

fetch('/files', {
  method: 'post',
  // what goes here? What is the "body" for this? content-type header?
}).then(/* whatever */);
deitch
źródło
1
oficjalny dokument działa dla mnie po tym, jak niektóre odpowiedzi nie powiodły się: developer.mozilla.org/en-US/docs/Web/API/Fetch_API/ ... coś może potwierdzić: 1. potrzebujesz pliku zawijania w FromData; 2. nie trzeba deklarować Content-Type: multipart/form-dataw nagłówku żądania
Spark.Bao

Odpowiedzi:

127

To jest podstawowy przykład z komentarzami. uploadFunkcją jest to, czego szukasz:

// 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);
Damien
źródło
8
Dlaczego ten przykład zawiera nagłówki Content-Type, ale inna odpowiedź mówi, aby je pominąć podczas wysyłania plików za pomocą Fetch API? Który to?
jjrabbit
12
NIE ustawiaj typu zawartości. Spędziłem dużo czasu, starając się, aby to działało, a następnie znalazłem ten artykuł, w którym napisano, że nie należy go ustawiać. I to działa! muffinman.io/uploading-files-using-fetch-multipart-form-data
Kostiantyn
jak odczytasz ten plik z, powiedzmy, zaplecza Express. Ponieważ plik nie jest wysyłany jako dane formularza. Zamiast tego jest wysyłany jako po prostu obiekt pliku. Czy express-fileupload lub multer analizuje takie ładunki?
sakib11
221

Zrobiłem to tak:

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
})
Integ
źródło
16
Nie musisz zawijać zawartości pliku w FormDataobiekt, jeśli wszystko, co przesyłasz, to plik (czego chce pierwotne pytanie). fetchzaakceptuje input.files[0]powyższe jako swój bodyparametr.
Klaus
17
Jeśli masz zaplecze PHP obsługujące przesyłanie plików, będziesz chciał opakować plik w FormData, aby tablica $ _FILES została prawidłowo wypełniona.
ddelrio1986
2
Zauważyłem również, że Google Chrome z jakiegoś powodu nie wyświetla pliku w ładunku żądania bez części FormData. Wygląda na błąd w panelu sieci przeglądarki Google Chrome.
ddelrio1986
4
To naprawdę powinna być prawidłowa odpowiedź. Drugi sposób też działa, ale jest bardziej
zawiły
co masz na myśli mówiąc / avatary? Czy odnosisz się do jakiegoś punktu końcowego interfejsu API zaplecza?
Kartikeya Mishra
90

Ważna uwaga dotycząca wysyłania plików za pomocą Fetch API

W content-typeżądaniu Fetch należy pominąć nagłówek. Następnie przeglądarka automatycznie doda Content typenagłówek zawierający wyglądającą granicę formularza

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

Granica formularza jest ogranicznikiem danych formularza

madhu131313
źródło
17
TO! Bardzo ważne! Nie używaj własnego typu zawartości do pobierania w wielu częściach. Nie miałem pojęcia, dlaczego mój kod nie działa.
Ernestas Stankevičius
1
To jest złoto! Zmarnowałem godzinę, nie rozumiejąc tego. Dzięki za podzielenie się tą wskazówką
Ashwin Prabhu,
1
Głosuj przeciw, bo chociaż to przydatne informacje, ale to nie jest próbą odpowiedzi na pytanie OP.
toraritte
3
To bardzo ważna informacja, która nie jest przechwytywana w dokumentacji MDN Fetch .
Plasty Grove
36

Jeśli chcesz mieć wiele plików, możesz użyć tego

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
})
Alex Montoya
źródło
@ Saly3301 Miałem ten sam problem, ponieważ moja funkcja API próbowała przekonwertować formData na JSON. (Skomentowałem tylko szansę, że komuś to pomaga)
mp035
19

Aby przesłać pojedynczy plik, możesz po prostu użyć Fileobiektu z input„s .filestablicy bezpośrednio jako wartość body:w fetch()inicjatora:

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

To działa, ponieważ Filedziedziczy po Blobi Blobjest jednym z dopuszczalnych BodyInittypów zdefiniowanych w Fetch Standard.

Mark Amery
źródło
To jest najprostsza odpowiedź, ale jak wpływa body: myInput.files[0]na ilość bajtów przechowywanych w pamięci po stronie klienta?
bhantol,
2
Chciałbym się spodziewać , że z tego rozwiązania przeglądarka byłoby sensowne wystarczy strumień plik i nie wymagają do odczytu do pamięci, @bhantol, ale nie wyszło mi z drogi, aby dowiedzieć się (albo empirycznie lub zagłębiając specyfikacja). Jeśli chcesz to potwierdzić, możesz spróbować (w każdej z głównych przeglądarek), używając tego podejścia, aby przesłać plik o rozmiarze 50 GB lub coś w tym stylu i sprawdzić, czy twoja przeglądarka próbuje użyć zbyt dużo pamięci i zostaje zabita.
Mark Amery,
Nie działa dla mnie. express-fileuploadnie udało się przeanalizować strumienia żądań. Ale FormDatadziała jak urok.
attacomsian
1
@attacomsian Na pierwszy rzut oka wydaje mi się, że express-fileuploadjest to biblioteka po stronie serwera do obsługi multipart/form-datażądań zawierających pliki, więc tak, nie jest zgodna z tym podejściem (które po prostu wysyła plik bezpośrednio jako treść żądania).
Mark Amery,
6

Przyjęta tutaj odpowiedź jest nieco przestarzała. Od kwietnia 2020 r. Zalecane podejście widoczne na stronie MDN sugeruje użycie, FormDataale także nie wymaga ustawiania typu treści.https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch

Dla wygody cytuję fragment kodu:

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);
});
Raiyan
źródło
1
Używanie FormDatabędzie działać tylko wtedy, gdy serwer oczekuje danych z formularza. Jeśli serwer chce pliku surowego jako treści POST, zaakceptowana odpowiedź jest poprawna.
Clyde
2

Odejście od podejścia Alexa Montoyi do wielu elementów wejściowych plików

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 })
Jerald Macachor
źródło
1

Problem polegał na tym, że użyłem response.blob () do wypełnienia danych formularza. Najwyraźniej nie możesz tego zrobić przynajmniej z React Native, więc ostatecznie użyłem

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

Wydaje się, że program Fetch rozpoznaje ten format i wysyła plik wskazany przez uri.

NickJ
źródło
0

Oto mój kod:

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>

Andreas Patsimas
źródło
1
Z recenzji: Cześć, nie odpowiadaj tylko za pomocą kodu źródłowego. Postaraj się przedstawić miły opis tego, jak działa Twoje rozwiązanie. Zobacz: Jak napisać dobrą odpowiedź? . Dzięki
sɐunıɔ ןɐ qɐp