Как дождаться обработки нескольких файлов перед вызовом готовой функции js

#javascript #es6-promise

Вопрос:

Следующая функция выполняется после операции перетаскивания нескольких файлов.

 function getFilesInfo(ev){
    for (let i = 0; i < ev.dataTransfer.items.length; i  ) {
        if (ev.dataTransfer.items[i].kind === 'file') {
            let file = ev.dataTransfer.items[i].getAsFile();
            //getFileInfo adds string to DOM element
            //Note the promise usage ...
            file.arrayBuffer().then((data)=>getFileInfo(file.name,data));
        }
    }
}
 

Я не могу понять, как вызвать функцию после завершения всех обещаний в этой функции.

В принципе, я хочу что-то вроде этого, последовательно:

 getFilesInfo(ev);
   //getFileInfo(<file1>);
   //getFileInfo(<file2>);
   //getFileInfo(<file3>);
   // etc.
//run only after all getFileInfo() calls have finished
processResults();
 

Сложность заключается в том, что чтение файлов создает обещание для каждого файла, который вызывается, когда файл был считан в память (часть arrayBuffer() вызова). Я не могу понять, как задержать processResults , потому getFilesInfo что завершается после того, как были запущены все вызовы чтения, а не (из того, что я могу сказать) после завершения getFileInfo функций.

Кажется, что, возможно, я мог бы каким-то образом добавить все вызовы ArrayBuffer в массив, а затем выполнить некоторые обещания (может быть?), Но это кажется неудобным, и я даже не уверен, как бы я это сделал.

Комментарии:

1. Что getFileInfo делает, что он возвращает и почему не processResults принимает аргументов?

2. » Я мог бы каким — то образом добавить все вызовы ArrayBuffer в массив, а затем выполнить связывание обещаний » — да, используйте Promise.all . Это совсем не неловко!

3. @Bergi getFileInfo обновляет строку в DOM, processResults считывает эту глобальную переменную и что-то с ней делает. Promise.all казалось уместным, но я не был уверен, как это реализовать с помощью цикла …

4.Добавьте все обещания, созданные с помощью file.arrayBuffer().then(…) , в массив, а затем вызовите Promise.all после цикла. И создайте getFileInfo return значение, не используйте глобальную переменную для передачи результатов processResults .

Ответ №1:

Вы можете использовать Promise.all , чтобы дождаться завершения массива обещаний:

 async function getFilesInfo(ev) {
  // create list of jobs
  const jobs = [];
  for (const item of ev.dataTransfer.items) {
    if (item.kind === 'file') {
      let file = item.getAsFile();
      jobs.push(file.arrayBuffer().then(data => {
        getFileInfo(file.name, data);
      }));
    }
  }
  // wait for all promise to fullfil
  await Promise.all(jobs);
}
 

https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Global_Objects/Promise/all

Ответ №2:

Вы могли бы сделать это таким образом:

 function getFilesInfo(ev){
    return ev.dataTransfer.items.filter(item=>item.kind === 'file').map(item=>{
        let file = item.getAsFile();
        return file.arrayBuffer().then((data)=>getFileInfo(file.name,data));
    });
}

Promise.all(...getFilesInfo(ev)).then(_=>{
    processResults();
});

// or with async/await
(async ()=>{
    await Promise.all(...getFilesInfo(ev));
    processResults();
})()
 

Ответ №3:

 async function getFilesInfo(ev) {
  await Promise.all(ev.dataTransfer.items.map(async (i) => {
    const file = i.getAsFile();
    const data = await file.arrayBuffer();
    return getFileInfo(file.name, data);
  }));
}

await getFilesInfo(ev); // will be awaited until all the promises are resolved
processResults();
 

Дайте мне знать, если это поможет.

Ответ №4:

Концептуальное препятствие, с которым я столкнулся, заключалось в том, что я думал о then функции как о возврате результатов, а не обещаний. Кроме того, многие примеры, которые я видел, Promise.all обычно представляют собой просто объединение явных вызовов, а не построение массива в цикле.

Как предложил Берги, я просто добавил вызовы в массив, а затем передал этот массив в Promise.all

 function getFilesInfo(ev) {
  // create list of jobs
  let jobs = [];
  for (const item of ev.dataTransfer.items) {
    if (item.kind === 'file') {
      let file = item.getAsFile();
      jobs.push(file.arrayBuffer().then(data => {
        getFileInfo(file.name, data);
      }));
    }
  }
  return jobs; 
}

//The call in the parent
let jobs = getFilesInfo(ev);
Promise.all(jobs).then(processResults);