Search code examples
javascriptangularpromisees6-promise

Resolving promises in JavaScript


I am having trouble getting promises to work the way I need. I have tried many different ways to resolve the promise but nothing I have done will work as I need. I am trying to get drag and drop of file working on a web page. I need a list of all the files in a Set (this.files) that is passed to a call to upload the files. The problem is that the Promise.all is being run before the promises are completed.

I am still struggling to wrap my mind around promises so maybe I have it all wrong but it seems from all my research this should work. Any help would be appreciated.

  async dndDropFiles(event) {
    event.preventDefault();
    let ptable = [];
    this.files = new Set();
    if (event.dataTransfer.types[0] === "Files") {
      var items = event.dataTransfer.items;
      for (var i=0; i<items.length; i++) {
        // webkitGetAsEntry is where the magic happens
        var item = await items[i].webkitGetAsEntry();
        if (item) {
          ptable.push(new Promise(async resolve => {
            resolve(await this.dndTraverseFileTree(item, ""));
          }))
        }
      }  
      Promise.all(ptable)
        .then( (results) => {
          if (this.files.size > 0) {
            this.progress = this.uploadService.upload(this.files, this.currentDir, this.currentProject);
          }    
        })
    }
  }

  async dndTraverseFileTree(item, path) {
    if (item.isFile) {
      // Get file
      item.file((file) => {
        this.files.add(file);
      });
  } else if (item.isDirectory) {
      // Get folder contents
      let ptable = [];
      var dirReader = await item.createReader();
      dirReader.readEntries((entries) => {
        for (var i=0; i<entries.length; i++) {
          ptable.push(new Promise( async resolve => {
            resolve(await this.dndTraverseFileTree(entries[i], path + item.name + "/"));
          }));
        }
        Promise.all(ptable)
          .then (results => {});
      });
    }
  }

Solution

  • It feels like you are making things a bit too difficult, and I suggest another good read into coding standards and the way Promises work :) The async/await construct has been introduced to increase code readability.

    Anyways, I have some untested code here. But it should do the trick. Also I strongly advise you to add typings. You are using angular, so I can only assume you are using TypeScript. With typings you will make less errors, and the compiler helps you along the way.

    Before I give the code, this webkitGetAsEntry is non standard. And should only be used if you really don't want to target old browsers or safari/ios:

    Non-standard This feature is non-standard and is not on a standards track. Do not use it on production sites facing the Web: it will not work for every user. There may also be large incompatibilities between implementations and the behavior may change in the future.

    But, you could go about it like this. First function to process the event. The second one to traverse the tree:

    async dndDropFiles(event: DragEvent): Promise<void> {
      if (event.dataTransfer.types[0] !== "Files" || !event.dataTransfer.items) {
        return;
      }
    
      const entries = [...(event.dataTransfer.items as any)].map(
        item => item.webkitGetAsEntry()
      );
    
      const allEntries = await this.dndTraverseFileTree(entries);
    
      const files = await Promise.all(
        allEntries.map(
          (entry) => new Promise((resolve, reject) => entry.file(resolve, reject))
        )
      );
    
      this.files = new Set(files);
    
      if (this.files.size > 0) {
        this.progress = this.uploadService.upload(
          this.files, this.currentDir, this.currentProject
        );
      }
    }
    
    async dndTraverseFileTree(entries: any[]): Promise<any[]> {
      const dirs = entries.filter(entry => !!entry && entry.isDirectory);
      const files = entries.filter(entry => !!entry && entry.isFile);
    
      if (dirs.length) {
        const childEntries = (
          await Promise.all(
              dirs.map(dir => new Promise(
                (resolve, reject) => dir.createReader().readEntries(resolve, reject))
              )
            )
        ).flat();
    
        return this.dndTraverseFileTree(childEntries);
      }
    
      return [ ...files ];
    }