Search code examples
typescriptrecursionpromise

How to make a recursive function type safe?


I have a component where the user can drag & drop files or folders onto it and the my code is doing something with that info.

Now I got a recursive method which is traversing a folder for files and other folders which could also have other folders with files in it and so on (I guess you get the idea). In the end I would like to have all the files which were dropped by the user.

Currently I got this method:

private traverseDirectory(entry: FileSystemDirectoryEntry): Promise<FileSystemEntry[]> {
    const reader = entry.createReader();
    return new Promise<FileSystemEntry[]>(resolveDirectory => {
      const iterationAttempts: Promise<FileSystemEntry>[] = [];
      const errorHandler = () => {
        logger.error('Error occurred while traversing directory.');
      };

      const readEntries = () => {
        reader.readEntries((batchEntries: FileSystemEntry[]) => {
          if (!batchEntries.length) {
            resolveDirectory(Promise.all(iterationAttempts));
          } else {
            batchEntries.forEach((batchEntry: FileSystemEntry) => {
              if (batchEntry.isDirectory) {
                iterationAttempts.push(
                  this.traverseDirectory(batchEntry as FileSystemDirectoryEntry) as any,
                );
              } else {
                iterationAttempts.push(Promise.resolve(batchEntry));
              }
            });
            readEntries();
          }
        }, errorHandler);
      };
      readEntries();
    });
  }

Now I would like to get rid of the " as any " part in the line after the isDirectory() condition.

I tried for several hours to come up with something but couldn't get it done and would appreciate some help.

Any advice on how to make the code maybe more readable or cleaner is very welcome as I am rather new to Angular and would like to improve my knowledge.

Thank you :)


Solution

  • Avoid the Promise constructor antipattern! Promisify only the readEntries method itself, then use promise chaining or even better clean async/await:

    private async traverseDirectory(directory: FileSystemDirectoryEntry): Promise<FileSystemFileEntry[]> {
      const reader = entry.createReader();
      const dirEntries = await new Promise<FileSystemEntry[]>((resolve, reject) => {
        reader.readEntries(resolve, reject);
      });
    
      const result: FileSystemFileEntry[] = [];
      for (const entry of dirEntries) {
        if (entry.isDirectory) {
          result.push(...await this.traverseDirectory(entry));
        } else if (entry.isFile) {
          result.push(entry);
        } else {
          console.warn(`Ignoring unsupported entry at ${entry.fullPath}`);
        }
      }
      return result;
    }