Export an async await variable and import it to another file once its done "awaiting" in JS

Viewed 1161

I just started coding in JS to make a website for my script, yet I'm having some noob issues.

I'm updating variables via an async await function connected to a fetch function. I want to render my variables in another script, and doing so has resulted in quiet the headache.

Here's what I have so far, but it seems like the second file is importing nul variables.

This is my computing and variable declaring file:

var url = 'https://financialmodelingprep.com/api/v3/profile/'+tickersymb+'?apikey='+api
var priceStat = "Working..."
var jsonData

function checkStats(url, callback) {
    return fetch(url)
        .then((response) => { 
            return response.json().then((data) => {
                console.log(data);
                return data;
            }).catch((err) => {
                console.log(err);
            }) 
        });
}

(async () => {
    jsonData = await checkStats(url)
    priceStat = jsonData.[0].price
    exports.jsonData = jsonData
    exports.priceStat = priceStat
    exports.tickersymb = tickersymb
})();

This is my rendering script:

var compute = require('components/compute-engine');

var pricestat = compute.pricestat;
var tickersymb = compute.tickersymb;
var jsonData = compute.jsonData;

export default function HeaderStats() {
  return (
    <>
      {/* Header */}
      <div className="relative bg-blue-600 md:pt-32 pb-32 pt-12">
        <div className="px-4 md:px-10 mx-auto w-full">
          <div>
            {/* Card stats */}

            <div className="flex flex-wrap">
              <div className="w-full lg:w-6/12 xl:w-3/12 px-4">
                <CardStats
                  statSubtitle=""
                  ticker= {tickersymb}
                  exchange="NASDAQ"
                  statIconName="fas fa-dollar-sign"
                  statIconColor="bg-green-500"
                />
...
1 Answers

There's no way to turn asynchronous code into synchronous code like this, either across modules or within a file. Once you step into an async chain, you can never get control back to sync code because all sync call stack frames need to finish before async functions get a chance to resolve.

As a consequence, you'll need to export a promise, then await it in the importer code.

There are a few patterns you could apply and I'm not sure which is best without seeing a larger design context, but I wouldn't attempt to use a promise for each of these individual variables--it doesn't make sense if you want to use all of them together (Promise.all could do that but it's not a client-friendly interface).

Instead, I'd recommend exporting a function, either checkStats directly, or a wrapper that does the indexing to prepare the data neatly for the consumer, returning the data packed into an object.

It appears your intent is to only fetch the data once. If you're worried that the function will keep hitting the API on every rerender, once a promise has resolved, further calls to await the promise just return the old value instead of re-computing it. In the sample code below, I only use one promise and store it in a variable so fetch is only called once regardless of how many times getStats is called.

Your importer should provide default values while awaiting promise resolution (you could export these as separate synchronous variables normally, but why should the data fetching module care about UI/view concerns? I'd keep it in the importer).

Lastly, it looks like you're using React, so the component needs to use this data in some kind of effect rather than in synchronous code outside the component. If you only want to fetch the data once, use [] as the second argument to useEffect or cache the data in a closure.

Here's a toy example to illustrate:

<script type="text/babel" defer>

/**** mocks ****/
const fetch = async () => {
  console.log("fetch call made");
  return {
    json: () => new Promise(resolve => 
      setTimeout(() => resolve([{price: 42}]), 1000)
    )
  };
};
const exports = {};
const require = () => exports;

/**** exporter file ****/
const url = "https://www.example.com";

const checkStats = url =>
  fetch(url)
    .then(response => response.json())
    .catch(err => console.error(err))
;

// cache the promise
const stats = checkStats(url)
  .then(jsonData => ({price: jsonData[0].price}))
;
exports.getStats = () => stats;

/**** importer file ****/
const {useState, useEffect} = React;
const {getStats} = require("./your-other-module");

const HeaderStats = () => {
  const [price, setPrice] = useState("loading...");

  useEffect(() => {
    getStats().then(data => setPrice(data.price));
  }, [price]);

  return (<h1>{price}</h1>);
};

// show that even with 2 renders, fetch is 
// only called once (open browser console)
ReactDOM.render(
  [<HeaderStats />, <HeaderStats />],
  document.body
);

</script>

<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.26.0/babel.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>

Related