0

I am building off of a forked React app. There is a dependency module that relies on a couple of static json files to map and export a couple of consts that are consumed in the app's React components. Instead of relying on these static json files, we're using an API as our source. Right now my build process is to fetch the json and transform it to generate our custom json files, which then gets consumed during the build process.

Instead, I want the process to be performed client side.

export const GALAXY = async () => {
    const result = await fetch(JSON
        , {
            headers: {
                'Content-Type': 'application/json',
                'Accept': 'application/json'
            }
        });
    if (result.ok) {
        console.log('GALAXY LOADED')
        return result.json();
    } else {
        console.log('GALAXY FAILED')
        return undefined
    }
}

It seems that anything that relies on an async/await function will only return a Promise. From the docs:

Return resolved promise from async function
Async functions always return a promise. If the return value of an async function is not explicitly a promise, it will be implicitly wrapped in a promise.

Most of the examples I've found show some variation of the .then pattern:

const galaxy = Promise.resolve(GALAXY()).then((value) => {
    console.log(value);
});

but if I try

const galaxy = Promise.resolve(GALAXY()).then((value) => {
    return value
});
console.log(galaxy);

I get Promise{pending}

I'm hoping to avoid rewriting a bunch of non-async code. We've got one TS file that gets imported by a number of React components:

import TokenMints from './token-mints.json';
export const TOKEN_MINTS: Array<{
    address: PublicKey;
    name: string;
}> = TokenMints.map((mint) => {
    return {
        address: new PublicKey(mint.address),
        name: mint.name,
    };
});

So how can I fetch from the JSON API and pass the data to this non-async const so that it can be used in the rest of the app?

dahifi
  • 77
  • 1
  • 7
  • Looks like you're wrapping GALAXY(), which already returns a promise in Promise.resolve which is doubling up on the promises. Why not just GALAXY().then()...? – Phix Nov 02 '21 at 23:41
  • @phix Not sure what you mean, even something like `const result = GALAXY().then(); console.log(result); ` still returns `Promise {pending}` – dahifi Nov 02 '21 at 23:50
  • Does this answer your question? [using async await and .then together](https://stackoverflow.com/questions/55019621/using-async-await-and-then-together) – Sysix Nov 02 '21 at 23:55
  • I mean wherever you import GALAXY you just use it like a regular promise. Don't wrap it in Promise.resolve() – Phix Nov 03 '21 at 00:35

2 Answers2

0

If you have something like this:

const example = async () => {
  return true
}

Then anywhere you call it or use it will result in a promise, that's how async works.

example().then(() => {
  // any code here will execute after the promise resolved
}

// any code here can not directly use the promise data as it's not resolved yet.  

The code inside then and outside then will execute in parallel, or at least the code outside it will continue executing while the promise resolves and once it finally resolves it will execute the code inside the then.

Alternatively you can use await.

const data = await example() // this forces all code execution to wait for the promise to resolve
// code here will not execute until that promise resolved

You have to correctly use async/await.

  • Right, so that forces me into the whole `Cannot use keyword 'await' outside an async function ` issue. So you're basically saying that I have to rewrite any downstream code to be async? That's a *lot* of downstream utility modules. – dahifi Nov 02 '21 at 23:55
  • If you want to use await you have to put that code inside another async function, so yea, you have to downstream async your code. Or you can put what you need in a .then and leave it at that. Welcome to reactive programming. –  Nov 02 '21 at 23:58
  • can you show me an example? I still can't figure out how to export a .then(). I can log from inside it, but returning anything gets wrapped in a promise. – dahifi Nov 03 '21 at 00:20
  • You can't export a .then... –  Nov 03 '21 at 00:22
0

Several possibilities to consider

  1. Fetch the JSON using a synchronous XMLHttpRequest operation. Use of this kind of synchronous requests in the main thread is, however, frowned upon (note).

  2. Convert the JSON to JavaScript source on the server (if required, in many/most cases JSON is valid JavaScript), assign it to a global variable and include it in the head section of the client page using <script> tags.

  3. A variation of (2): convert the JSON to a module file on the server which exports the JSON data converted to JavaScipt source, which client code includes as a module, and from which imports the data as a JavaScript object.

  4. If the client code uses the data after waiting for an event such as DOMContentLoaded (then calling a main function rather than writing the app in file level code), you could promisify the DomContentLoaded event arrival, and pass it through Promise.all along with the galaxy promise before calling main, along the lines of

     Promise.all(
         new Promise( resolve=>
             document.addEventListener("DOMContentLoaded", resolve)
         ),
        galaxy
     ).then( data => main( data[1])); // passing galaxy value
    

About async

As already noticed, async functions return a promise for the value syntactically returned in the function body - because async functions return synchronously when called, without waiting for any asynchronous operations performed in the function body to complete. Given JavaScript is single threaded, the caller is not put on hold until all operations have been carried out, and must return to the event loop for other pieces of JavaScript code to execute.

traktor
  • 17,588
  • 4
  • 32
  • 53
  • Well, I know our app should fail gracefully, but if the fetch fails then we can fall back to a cached copy. The app literally can't run without the data. As I said in the OP, right now we're storing the JSON as source, but the problem is we have to `yarn build` the entire site to get the updated data. If this is The Way, then fine, but I spent a lot of time updating our React components using the same API and just frustrated that we can't use the same provider in our static modules. Effort wasted, basically. – dahifi Nov 03 '21 at 01:17
  • You can't use `fetch` to get data from the server synchronously. That leaves in-lining data in the HTML page, serving it as JavaScript (not JSON) from a dynamic endpoint on the server and including it client-side using script tags, using synchronous XMLHttpRequests, or using [`importScripts'](https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope/importScripts) in a webworker. If none of these fit your needs, asynchronous retrieval will be required. – traktor Nov 03 '21 at 02:07