Exporting asyc dependent, non-async const from JS module - javascript

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?

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.

Several possibilities to consider
Fetch the JSON using a synchronous XMLHttpRequest operation. Use of this kind of synchronous requests in the main thread is, however, frowned upon (note).
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.
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.
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.

Related

Get number of open orders for a symbol using Binance's Node.js API

I am using Binance's Node.js API. It says regarding "Get open orders for a symbol", I should do:
binance.openOrders("ETHBTC", (error, openOrders, symbol) => {
console.info("openOrders("+symbol+")", openOrders);
});
To print out number of open orders, I do:
binance.openOrders("ETHBTC", (error, openOrders, symbol) => {
console.info(openOrders.length);
});
which works and the number gets printed out. However, I would need this result to be stored in a variable which can be used later by other functions. Building on SO's Javascript chat room, I do:
let OO =
(async() => {
const openOrders = await binance.openOrders(false);
return openOrders.length
})()
console.log(OO)
This however prints
Promise { <pending> }
only.
I have seen several other questions discussing Promise { <pending> } issue but I haven't been able to implement their solutions to this specific case.
How could I get number of open orders into a variable accessible by other functions?
You'll need to use either completely async approach or use callbacks.
The last block in your question shows exactly what this answer explains. Javascript doesn't wait for Promise to resolve/reject in a synchronous context. So your "async" block returned the unresolved Promise and the rest of your (synchronous) code didn't wait for it to resolve.
Example of using async functions
const getOpenOrdersCount = async () => {
const openOrders = await binance.openOrders("ETHBTC");
return openOrders.length;
};
const run = async () => {
const openOrdersCount = await getOpenOrdersCount();
console.log(openOrdersCount);
}
Note: You can use await only within async functions.
Example of using callbacks is your code. They are useful in a small scope, but in bigger scope they get messy and turn into a callback hell. So I wouldn't recommend using callbacks in a bigger scope.
binance.openOrders("ETHBTC", (error, openOrders, symbol) => {
console.info(openOrders.length);
// here goes rest of your code that needs to use the `openOrders` variable
});

Problem with reading data from Cache API (still in promise?)

That is my first post here. I am not well skilled in asynchronous code so can not resolve the problem by myself
In a React/Redux app I have added cache. The idea behind it is to have something like 'Favorites' functionality but on clients' computer. So, I would like to store over there some data about books. While writing to cache works I can not successively dispatch data to store> Now my code looks like this:
export function fetchFromFavorites() {
return async (dispatch, getState) => {
const URL = getState().books.currentURL;
const Cache = await caches.open(URL);
Cache.matchAll()
.then(function (response) {
const ar = [];
response.forEach(async item => ar.push(await item.json()));
return ar;
})
.then(response => dispatch(test(response)));
};
}
In the code above test is an action that only sets the state field with payload. While the payload can be log-consoled from reducer, I can not perform on that any further action with another external function, well-checked on that kind of data. Besides DevTools mark it with blue 'i' what indicates that it has been calculated very lately. What is wrong with that code? BTW - it has nothing to do with service workers it is just inside regular React.
The function you are passing to response.forEach is returning a promise. You'd need to wait for all of those promises to resolve before returning ar.
For example, you may use something like:
// await all promises to be fulfilled before proceeding.
// note that we use response.map instead of forEach as
// we want to retain a reference to the promise returned
// by the callback.
// Additionally, we can just return the promise returned
// by `item.json()`
await Promise.all(response.map(item => item.json());
Remember, any function marked as async will return a promise wrapping the function's return type.
Note that you're mixing async/await with older style then/catch promises here. For consistency and ease of reading, you may want to use one style consistently.

From queue() to Promise [duplicate]

I try to load data from two different sources. After loading the data I want to use it within a riot tag file. But I do not understand how to load the second file, because I do not really understand the asynchronous call.
What do I have to modify in my code to get the data? Right now, the second data object is undefined. Here is my code:
import { csv, json } from 'd3-fetch'
csv('/data/stations.csv', function (stations) {
json('data/svg_data.json', function (svg) {
return svg
})
stations.position_x = +stations.position_x
stations.position_y = +stations.position_y
stations.animation_time = +stations.animation_time
stations.text_x = +stations.text_x
stations.text_y = +stations.text_y
return stations
}).then(function (stations, svg) {
mount('metro-app', {
stations: stations,
svg_data: svg
})
})
The d3-fetch module makes use of the Fetch API and will, therefore, return a Promise for each request issued via one of the module's convenience methods. To load multiple files at once you could use Promise.all which will return a single Promise that resolves once all Promises provided to the call have resolved.
import { csv, json } from 'd3-fetch'
Promise.all([
csv('/data/stations.csv'),
json('data/svg_data.json')
])
.then(([stations, svg]) => {
// Do your stuff. Content of both files is now available in stations and svg
});
Here, d3.csv and d3.json are provided to fetch content from two files. Once both requests have completed, i.e. both Promises have resolved, the content of each file is provided to the single Promise's .then() method call. At this point you are able to access the data and execute the rest of your code.

Return SVG from a function

I'm trying to return an SVG file located in the same folder as the Javascript file when a function is executed.
The function is called returnSVG, the SVG is called image.
import image from './image.svg'
returnSVG= () => {
return image;
}
But when I call it I get this: /static/media/image.f85cba53.svg
An SVG file contains a textual representation of a graphic, encoded in an XML-like syntax.
So, you cannot simply import thing from 'filepath' because the file contents are not a JavaScript module, a loadable binary extension, or a JSON-encoded string.
Rather than importing what cannot be imported, the correct approach would be to explicitly read the contents from the file:
const { readFileSync } = require('fs')
const returnSvg = (path = './image.svg') => return readFileSync(path)
Note that this example uses the synchronous version of the fs function to read a file. This will pause (block) all processing in NodeJS until the contents have been read.
The better solution would be to use Promises via async/await as in:
const { readFile } = require('fs')
const { promisify } = require('util')
const asyncReadFile = promisify(readFile)
const returnSvg = async (path = './image.svg') => {
const data = await asyncReadFile(path)
// since fs.readFile returns a buffer, we should probably convert it to a string.
return data.toString()
}
Using Promises and async/await requires that all of your processing occur within the chain of promises, so you'd need to restructure your code to:
returnSvg()
.then(data => { /* do something with the file contents here. */ })
.catch(err => console.error(`failed to read svg file: ${err}`))
Remember, in NodeJS, operations can be either asynchronous (via callbacks or Promises which async/await uses "under the covers"), or synchronous (via special "blocking" calls).
One option, if you choose to use the synchronous version would be to load all your files into your server before you call app.listen(8080, () => console.log('listing on 8080') but this implies you know what files will be required before you run your server and would not work for dynamically loaded files.
You might imagine doing something like:
const data = readSvg()
console.log(data)
or perhaps:
let data
readSvg().then(res => data = res)
console.log(data)
but neither will work because an asynchronous function (one defined with the async keyword) can only return a Promise.
Both attempts will not print any usable value to the console because at the time console.log() is called, NodeJS has no idea what the value of data will be.
The rule of thumb here is:
You cannot return a value or interact with any higher context from within a Promise.
Any manipulation of the data generated within a Promise chain, can ONLY be accessed from within its own chain.

Passing context implicitly across functions and javascript files in nodes.js

I have created a web server i node.js using express and passport. It authenticates using an oauth 2.0 strategy (https://www.npmjs.com/package/passport-canvas). When authenticated, I want to make a call such as:
app.get("/api/courses/:courseId", function(req, res) {
// pass req.user.accessToken implicitly like
// through an IIFE
createExcelToResponseStream(req.params.courseId, res).catch(err => {
console.log(err);
res.status(500).send("Ops!");
});
});
My issue is that i would like, in all subsequent calls from createExcelToResponseStream, to have access to my accessToken. I need to do a ton of api calls later in my business layer. I will call a method that looks like this:
const rq = require("request");
const request = url => {
return new Promise(resolve => {
rq.get(
url,
{
auth: {
bearer: CANVASTOKEN // should be req.user.accessToken
}
},
(error, response) => {
if (error) {
throw new Error(error);
}
resolve(response);
}
);
});
};
If i try to create a global access to the access token, i will risk
race conditions (i think) - i.e. that people get responses in the context of another persons access token.
If i pass the context as a variable i have to refactor a
lof of my code base and a lot of business layer functions have to
know about something they don't need to know about
Is there any way in javascript where i can pass the context, accross functions, modules and files, through the entire callstack (by scope, apply, bind, this...). A bit the same way you could do in a multithreaded environment where you have one user context per thread.
The only thing you could do would be
.bind(req);
But that has has to be chained into every inner function call
somefunc.call(this);
Or you use inline arrow functions only
(function (){
inner=()=>alert(this);
inner();
}).bind("Hi!")();
Alternatively, you could apply all functions onto an Object, and then create a new Instance:
var reqAuthFunctions={
example:()=>alert(this.accessToken),
accessToken:null
};
instance=Object.assign(Object.create(reqAuthFunctions),{accessToken:1234});
instance.example();
You could use a Promise to avoid Race conditions.
Let's have this module:
// ContextStorage.js
let gotContext;
let failedGettingContext;
const getContext = new Promise((resolve,reject)=>{
gotContext = resolve;
failedGettingContext = reject;
}
export {getContext,gotContext, failedGettingContext};
And this inititalization:
// init.js
import {gotContext} from './ContextStorage';
fetch(context).then(contextIGot => gotContext(contextIGot));
And this thing that needs the context:
// contextNeeded.js
import {getContext} from './ContextStorage';
getContext.then(context => {
// Do stuff with context
}
This is obviously not very usable code, since it all executes on load, but I hope it gives you a framework of how to think about this issue with portals... I mean Promises...
The thing that happens when you call the imported 'gotContext', you actually resolve the promise returned by 'getContext'. Hence no matter the order of operations, you either resolve the promise after the context has been requested setting the dependent operation into motion, or your singleton has already a resolved promise, and the dependent operation will continue synchronously.
On another note, you could easily fetch the context in the 'body' of the promise in the 'ContextStorage' singleton. However that's not very modular, now is it. A better approach would be to inject the initializing function into the singleton in order to invert control, but that would obfuscate the code a bit I feel hindering the purpose of the demonstration.

Categories