Wie lade ich eine Datei mit der JS-Abruf-API hoch?

170

Ich versuche immer noch, meinen Kopf darum zu wickeln.

Ich kann den Benutzer die Datei (oder sogar mehrere) mit der Dateieingabe auswählen lassen:

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

Und ich kann das submitEreignis mit verfolgen <fill in your event handler here>. Aber wie sende ich die Datei mit fetch?

fetch('/files', {
  method: 'post',
  // what goes here? What is the "body" for this? content-type header?
}).then(/* whatever */);
Gottheit
quelle
1
Das offizielle Dokument funktioniert für mich, nachdem einige Antworten fehlgeschlagen sind: developer.mozilla.org/en-US/docs/Web/API/Fetch_API/… , etwas kann bestätigen: 1. Wrap-Datei in FromData benötigen; 2. muss nicht Content-Type: multipart/form-dataim Anforderungsheader deklarieren
Spark.Bao

Antworten:

127

Dies ist ein einfaches Beispiel mit Kommentaren. Die uploadFunktion ist genau das, wonach Sie suchen:

// 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
quelle
8
Warum enthält dieses Beispiel Content-Type-Header, aber eine andere Antwort besagt, dass diese beim Senden von Dateien mit der Fetch-API weggelassen werden sollen? Welches ist es?
jjrabbit
12
Stellen Sie NICHT den Inhaltstyp ein. Ich habe viel Zeit damit verbracht, es zum Laufen zu bringen, und dann diesen Artikel gefunden, in dem es heißt, es nicht einzustellen. Und es funktioniert! muffinman.io/uploading-files-using-fetch-multipart-form-data
Kostiantyn
Wie würden Sie diese Datei aus dem Express-Backend lesen? Da die Datei nicht als Formulardaten gesendet wird. Es wird stattdessen nur als Dateiobjekt gesendet. Analysiert Express-Fileupload oder Multer solche Payloads?
Sakib11
221

Ich habe es so gemacht:

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
quelle
16
Sie müssen den Dateiinhalt nicht in ein FormDataObjekt einschließen, wenn Sie nur die Datei hochladen (was die ursprüngliche Frage wünscht). fetchwird input.files[0]oben als bodyParameter akzeptieren .
Klaus
17
Wenn Sie über ein PHP-Backend verfügen, das den Datei-Upload verwaltet, sollten Sie die Datei in FormData einschließen, damit das Array $ _FILES ordnungsgemäß ausgefüllt wird.
ddelrio1986
2
Ich habe auch festgestellt, dass Google Chrome die Datei ohne den FormData-Teil aus irgendeinem Grund nicht in der Anforderungsnutzlast anzeigen würde. Scheint ein Fehler im Netzwerkfenster von Google Chrome zu sein.
ddelrio1986
4
Dies sollte wirklich die richtige Antwort sein. Der andere Weg funktioniert auch, ist aber
komplizierter
Was meinst du mit / Avatare? Beziehen Sie sich auf einen Backend-API-Endpunkt?
Kartikeya Mishra
90

Ein wichtiger Hinweis zum Senden von Dateien mit der Fetch-API

Der content-typeHeader für die Abrufanforderung muss weggelassen werden. Dann fügt der Browser automatisch die Content typeKopfzeile einschließlich der Formulargrenze hinzu, die aussieht

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

Die Formulargrenze ist das Trennzeichen für die Formulardaten

madhu131313
quelle
17
DIES! Sehr wichtig! Verwenden Sie keinen eigenen Inhaltstyp mit Abruf auf mehreren Teilen. Ich hatte keine Ahnung, warum mein Code nicht funktioniert.
Ernestas Stankevičius
1
Das ist Gold! Ich habe 1 Stunde damit verbracht, das nicht zu verstehen. Vielen Dank für diesen Tipp
Ashwin Prabhu
1
Downvote, denn obwohl es nützliche Informationen sind, versucht dies nicht, die Frage von OP zu beantworten.
Toraritte
3
Dies sind sehr wichtige Informationen, die nicht in den MDN-Abrufdokumenten erfasst werden .
Plasty Grove
36

Wenn Sie mehrere Dateien möchten, können Sie diese verwenden

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
quelle
@ Saly3301 Ich hatte das gleiche Problem, weil meine API-Funktion versuchte, die formData in JSON zu konvertieren. (Ich habe nur die Chance kommentiert, dass es jemandem hilft)
mp035
19

Um eine einzelne Datei einreichen, können Sie einfach die Verwendung FileObjekt aus dem inputs‘ .filesArray direkt als Wert body:in Ihrem fetch()initializer:

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

Dies funktioniert, weil er von Fileerbt Blobund Blobeiner der im Abrufstandard BodyInitdefinierten zulässigen Typen ist.

Mark Amery
quelle
Dies ist die einfachste Antwort, aber wie wirkt sich dies body: myInput.files[0]auf die Anzahl der auf der Clientseite im Speicher gespeicherten Bytes aus?
Bhantol
2
Ich würde erwarten, dass der Browser mit dieser Lösung sinnvoll genug ist, um die Datei zu streamen und nicht zu verlangen, dass sie in den Speicher @bhantol eingelesen wird, aber ich habe mir nicht die Mühe gemacht, dies herauszufinden (weder empirisch noch durch Eingreifen) die Spezifikation). Wenn Sie dies bestätigen möchten, können Sie (in jedem der wichtigsten Browser) versuchen, mit diesem Ansatz eine 50-GB-Datei oder etwas anderes hochzuladen und festzustellen, ob Ihr Browser versucht, zu viel Speicher zu verwenden, und dabei getötet wird.
Mark Amery
Hat bei mir nicht funktioniert. express-fileuploadDer Anforderungsdatenstrom konnte nicht analysiert werden. Funktioniert aber FormDatawie ein Zauber.
Attacomsian
1
@attacomsian Auf den ersten Blick sieht es für mich so aus, als wäre express-fileuploades eine serverseitige Bibliothek für die Verarbeitung von multipart/form-dataAnforderungen, die Dateien enthalten. Ja, sie ist nicht mit diesem Ansatz kompatibel (der die Datei nur direkt als Anforderungshauptteil sendet).
Mark Amery
6

Die hier akzeptierte Antwort ist etwas veraltet. Ab April 2020 schlägt ein empfohlener Ansatz auf der MDN-Website die Verwendung vor FormDataund fordert auch nicht dazu auf, den Inhaltstyp festzulegen. https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch

Ich zitiere das Code-Snippet der Einfachheit halber:

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
quelle
1
Die Verwendung FormDatafunktioniert nur, wenn der Server Formulardaten erwartet. Wenn der Server eine Rohdatei als Hauptteil des POST wünscht, ist die akzeptierte Antwort korrekt.
Clyde
2

Abspringen von Alex Montoyas Ansatz für mehrere Dateieingabeelemente

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
quelle
1

Das Problem für mich war, dass ich ein response.blob () verwendete, um die Formulardaten zu füllen. Anscheinend kann man das zumindest nicht mit native Reaktion tun, also habe ich es letztendlich benutzt

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

Fetch scheint dieses Format zu erkennen und die Datei zu senden, auf die der Uri zeigt.

NickJ
quelle
0

Hier ist mein Code:

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
quelle
1
Aus der Bewertung: Hallo, bitte antworten Sie nicht nur mit dem Quellcode. Versuchen Sie, eine schöne Beschreibung der Funktionsweise Ihrer Lösung zu geben. Siehe: Wie schreibe ich eine gute Antwort? . Danke
sɐunıɔ ןɐ qɐp