Jimbo
Jimbo

Reputation: 3284

How to wait until multiple files are processed before calling finished function js

The following function runs after a drag and drop operation of multiple files.

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

I can't figure out how to call a function after all of the promises in this function finish.

Basically I want something like this, sequentially:

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

The tricky part is that reading the files generates a promise for each file that gets called when the file has been read into memory (part of the arrayBuffer() call). I can't figure out how to delay processResults because getFilesInfo finishes after all of the read calls have been triggered, not (from what I can tell), after the getFileInfo functions have finished.

It seems like perhaps I could somehow add all arrayBuffer calls to an array and then do some promise chaining (maybe?) but that seems awkward and I'm not even sure how I would do that.

Upvotes: 0

Views: 1104

Answers (4)

Jimbo
Jimbo

Reputation: 3284

The conceptual hurdle I was running into was that I was thinking of the then function as returning the results, not promises. Also, many of the examples I've seen with Promise.all are usually just concatenating explicit calls, not building an array in a loop.

As suggested by Bergi, I simply added the calls to an array, and then passed that array into 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);

Upvotes: 1

8HoLoN
8HoLoN

Reputation: 1142

You could do it that way:

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();
})()

Upvotes: 1

msrumon
msrumon

Reputation: 1430

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();

Let me know if that helps.

Upvotes: 0

Etienne Dldc
Etienne Dldc

Reputation: 169

You can use Promise.all to wait for an array of promise to finish:

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

Upvotes: 1

Related Questions