0

I'm trying to understand the relationship between async/await and Promises in TypeScript and have gotten myself a but too confused, I think.

Im particular, I'm trying to understand how the async/await version of an example function, downloadFileAA, defined below maps to an implementation that uses Promises instead. What is going in behind the scenes with Promises? Obviously neither of my Promise versions are satisfactory or entirely capture what's going on in the async/await version (downloadFilePromise never reports success or failure, and downloadFilePromise2 requires some weird redundancy: result.then() in the argument to resolve to pass type checking), but I'm not certain why.

What would a Promise-based version of downloadFileAA look like? Is there a reason to prefer it (or not) over the async/await version?

import * as FileSystem from 'expo-file-system'

const callback = ( downloadProgress ) => {
    const progress = downloadProgress.totalBytesWritten / downloadProgress.totalBytesExpectedToWrite
    console.log(
        `${Number( progress )
            .toLocaleString( undefined, { style: 'percent', minimumFractionDigits: 0 } )
            .padStart( 5, ' ' )} of ${downloadProgress.totalBytesExpectedToWrite} bytes`
    )
}

const downloadResumable = FileSystem.createDownloadResumable(
    'http://...',
    FileSystem.documentDirectory + 'somebigfile',
    {},
    callback
)

const downloadFileAA = async () => {
    try {
        console.log( 'Starting download (with async/await)... ' )
        const result = await downloadResumable.downloadAsync()
        console.log( result ? `Finished downloading to ${result.uri}` : 'Undefined result?'  )
    } catch ( e ) {
        console.error( `Failed download: ${e.message}` )
    }
}

const downloadFilePromise = () => {
        console.log( 'Starting download (with Promise)... ' )
        new Promise<FileSystem.FileSystemDownloadResult>(  ( resolve, reject ) =>
            downloadResumable.downloadAsync()
         )
            .then( ( result ) => console.log( `Finished downloading to ${result.uri}` ) )
            .catch( ( reason ) => console.error( `Failed download: ${reason}` ) )
}

const downloadFilePromise2 = () => {
        console.log( 'Starting download (with Promise Two)... ' )
        new Promise<FileSystem.FileSystemDownloadResult>(  ( resolve, reject ) => {
            const result = downloadResumable.downloadAsync()
            result  ? resolve( result.then() ) : reject( result )
        } )
            .then( ( result ) => console.log( `Finished downloading to ${result.uri}` ) )
            .catch( ( reason ) => console.error( `Failed download: ${reason}` ) )
}
orome
  • 45,163
  • 57
  • 202
  • 418
  • https://www.geeksforgeeks.org/difference-between-promise-and-async-await-in-node-js/#:~:text=Promise%20is%20an%20object%20representing,the%20code%20execute%20more%20synchronously. – vanowm May 07 '22 at 22:07
  • @vanowm Yes, lol, that's exactly what got me started trying to translate from async/await to Promises, but as you can see I didn't get very far. – orome May 07 '22 at 22:09
  • `async`/`await` is not syntactic sugar for `new Promise`, but only for `.then(…)`. – Bergi May 07 '22 at 23:02

1 Answers1

3

Assuming that downloadAsync returns a Promise and that console.log doesn't throw, this

const downloadFileAA = async () => {
    try {
        console.log( 'Starting download (with async/await)... ' )
        const result = await downloadResumable.downloadAsync()
        console.log( result ? `Finished downloading to ${result.uri}` : 'Undefined result?'  )
    } catch ( e ) {
        console.error( `Failed download: ${e.message}` )
    }
}

is equivalent to

const downloadFileAA = () => {
    console.log('Starting download (with async/await)... ')
    return downloadResumable.downloadAsync()
        .then((result) => {
            console.log(result ? `Finished downloading to ${result.uri}` : 'Undefined result?')
        })
        .catch((e) => {
            console.error(`Failed download: ${e.message}`);
        });
};

What await does is it replaces .then. It doesn't replace Promises - both .then and await require a Promise (to be used sensibly, at least).

Your downloadFilePromise function is broken because it constructs a Promise that, by definition, never resolves - you must call the resolve or reject argument in order for the constructed Promise to resolve. But since it looks like downloadAsync already returns a Promise, constructing another Promise to surround it doesn't make any sense - just use the Promise you have instead of making a new one.

Is there a reason to prefer it (or not) over the async/await version?

It's purely a stylistic choice. IMO await shines best when there are multiple values that need to be waited for (serially), but otherwise either work just fine (as long as they're implemented properly, of course).

CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
  • 1
    As an addendum, I would say the caveat "as long as they're implemented properly" is an important one. One of the main strengths of async-await is that control flow is very easy to visualise, whereas with `.then()` chains, it's easy to overlook a chain with no catcher etc. So while yes, both can be used to write equivalent code, in my personal opinion async-await makes mistakes easier to spot. And since we all make mistakes when writing code, that's a very good thing. – Etheryte May 07 '22 at 22:15
  • 1
    You should return `downloadResumable.downloadAsync` in the second example otherwise this can't be awaited. – lawrence-witt May 07 '22 at 22:16
  • @lawrence-witt In `downloadFilePromise`? How does that fix it? – orome May 08 '22 at 17:45