Comment télécharger un fichier avec l'API JS fetch?

171

J'essaye toujours d'enrouler ma tête autour de ça.

Je peux demander à l'utilisateur de sélectionner le fichier (ou même plusieurs) avec l'entrée de fichier:

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

Et je peux attraper l' submitévénement en utilisant <fill in your event handler here>. Mais une fois que je le fais, comment envoyer le fichier en utilisant fetch?

fetch('/files', {
  method: 'post',
  // what goes here? What is the "body" for this? content-type header?
}).then(/* whatever */);
deitch
la source
1
le document officiel fonctionne pour moi après avoir essayé quelques réponses échouées: developer.mozilla.org/en-US/docs/Web/API/Fetch_API/… , quelque chose peut confirmer: 1. besoin d'un fichier wrap dans FromData; 2. n'a pas besoin de déclarer Content-Type: multipart/form-datadans l'en-tête de la demande
Spark.Bao

Réponses:

127

Ceci est un exemple de base avec des commentaires. La uploadfonction est ce que vous recherchez:

// 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
la source
8
Pourquoi cet exemple inclut-il des en-têtes Content-Type mais une autre réponse dit de les omettre lors de l'envoi de fichiers avec Fetch API? Laquelle est-ce?
jjrabbit
12
Ne définissez PAS Content-Type. J'ai passé beaucoup de temps à essayer de le faire fonctionner, puis j'ai trouvé cet article disant de ne pas le régler. Et il fonctionne! muffinman.io/uploading-files-using-fetch-multipart-form-data
Kostiantyn
comment liriez-vous ce fichier à partir du backend, disons Express. Comme le fichier n'est pas envoyé sous forme de données de formulaire. Il est plutôt envoyé en tant qu'objet fichier. Express-fileupload ou multer analyse-t-il ces charges utiles?
sakib11 le
221

Je l'ai fait comme ça:

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
la source
16
Vous n'avez pas besoin d'encapsuler le contenu du fichier dans un FormDataobjet si tout ce que vous téléchargez est le fichier (ce que veut la question d'origine). fetchacceptera input.files[0]ci-dessus comme bodyparamètre.
Klaus
17
Si vous avez un backend PHP qui gère le téléchargement du fichier, vous voudrez envelopper le fichier dans un FormData afin que le tableau $ _FILES soit correctement rempli.
ddelrio1986
3
J'ai également remarqué que Google Chrome n'afficherait pas le fichier dans la charge utile de la demande sans la partie FormData pour une raison quelconque. Cela ressemble à un bogue dans le panneau Réseau de Google Chrome.
ddelrio1986
4
Cela devrait vraiment être la bonne réponse. L'autre façon fonctionne aussi mais est plus compliquée
jnmandal
qu'entendez-vous par / avatars? Faites-vous référence à un point de terminaison d'API backend?
Kartikeya Mishra
90

Une note importante pour l'envoi de fichiers avec Fetch API

Il faut omettre l'en- content-typetête pour la requête Fetch. Ensuite, le navigateur ajoutera automatiquement l'en- Content typetête, y compris la limite du formulaire qui ressemble à

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

La limite du formulaire est le délimiteur des données du formulaire

madhu131313
la source
17
CE! Très important! N'utilisez pas votre propre type de contenu avec la récupération en plusieurs parties. Je ne savais pas pourquoi mon code ne fonctionnait pas.
Ernestas Stankevičius
1
C'est de l'or! J'ai perdu 1 heure à ne pas comprendre cela. Merci d'avoir partagé cette astuce
Ashwin Prabhu
1
Downvote parce que même si ce sont des informations utiles, mais cela ne tente pas de répondre à la question d'OP.
toraritte
3
Il s'agit d'informations très importantes qui ne sont pas capturées dans les documents MDN Fetch .
Plasty Grove
36

Si vous voulez plusieurs fichiers, vous pouvez utiliser ceci

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
la source
@ Saly3301 J'ai eu le même problème, c'était parce que ma fonction API essayait de convertir le formData en JSON. (Je n'ai commenté que si cela aide quelqu'un)
mp035
19

Pour soumettre un seul fichier, vous pouvez simplement utiliser l' Fileobjet du tableau de input's .filesdirectement comme valeur de body:dans votre fetch()initialiseur:

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

Cela fonctionne car Filehérite de Blobet Blobest l'un des BodyInittypes autorisés définis dans la norme Fetch.

Mark Amery
la source
C'est la réponse la plus simple, mais comment cela body: myInput.files[0]cause-t-il la quantité d'octets conservés en mémoire côté client?
bhantol
2
Je m'attendrais à ce qu'avec cette solution, le navigateur soit suffisamment judicieux pour diffuser le fichier et ne pas exiger qu'il soit lu en mémoire, @bhantol, mais je n'ai pas fait tout mon possible pour le savoir (soit empiriquement, soit en explorant la spécification). Si vous souhaitez confirmer, vous pouvez essayer (dans chacun des principaux navigateurs) d'utiliser cette approche pour télécharger un fichier de 50 Go ou quelque chose du genre, et voir si votre navigateur essaie d'utiliser trop de mémoire et se fait tuer.
Mark Amery
N'a pas travaillé pour moi. express-fileuploadéchec de l'analyse du flux de demandes. Mais FormDatafonctionne comme un charme.
attacomsian
1
@attacomsian En un coup d'œil, il me semble que express-fileuploadc'est une bibliothèque côté serveur pour gérer les multipart/form-datarequêtes contenant des fichiers, donc oui, ce n'est pas compatible avec cette approche (qui envoie juste directement le fichier en tant que corps de la requête).
Mark Amery
6

La réponse acceptée ici est un peu datée. À partir d'avril 2020, une approche recommandée vue sur le site Web de MDN suggère d'utiliser FormDataet ne demande pas non plus de définir le type de contenu. https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch

Je cite l'extrait de code pour plus de commodité:

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
la source
1
L'utilisation FormDatane fonctionnera que si le serveur attend des données de formulaire. Si le serveur veut un fichier brut comme corps du POST, la réponse acceptée est correcte.
Clyde
2

Sauter de l'approche d'Alex Montoya pour plusieurs éléments d'entrée de fichier

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
la source
1

Le problème pour moi était que j'utilisais un response.blob () pour remplir les données du formulaire. Apparemment, vous ne pouvez pas faire cela au moins avec React Native, alors j'ai fini par utiliser

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

Fetch semble reconnaître ce format et envoyer le fichier vers lequel pointe l'URI.

NickJ
la source
0

Voici mon 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
la source
1
De l'avis: Bonjour, ne répondez pas uniquement avec le code source. Essayez de fournir une belle description du fonctionnement de votre solution. Voir: Comment écrire une bonne réponse? . Merci
sɐunıɔ ןɐ qɐp