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.
Related
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.
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.
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.
I have a asynchronous function (a promise), which reads an xml file and give a map of keys and value, if the promise is resolved. I need this map throughout my project. So i want to read this xml file only once, probably at the start of my project and save the map in a variable. And i want to use the variable in different files of my project. Is there any way by which i can achieve this?
You can define the functions in files to expect a parameter, use Promise.all() to load the files, pass the function reference from requested files to .then() within .then() chained to Promise.all()
const promise = Promise.resolve("do stuff");
promise.then(res => {
Promise.all([fetch("loadFile1.js").then(res => res.text())
/* , fetch("loadFile2.js")*/])
.then(function(files) {
let script = document.createElement("script");
script.textContent = files[0];
document.body.appendChild(script);
promise.then(loadFile)
})
});
where loadFiles1.js contains a function loadFile
function loadFile(data) {
console.log(data);
}
plnkr http://plnkr.co/edit/SQzakbP936rEgnEzsP8R?p=preview
Let's say in an Angular app, I have a controller which outputs a list of data, formatted according to configured rules. Both data should be fetched asynchronously:
list config (for instance, which attributes to be displayed)
data
Alright, since both data (config and data) are retrieved from a backend using $http/ngResource/Restangular, the code may look like this:
angular.module('myApp').controller('ListCtrl', function (Backend) {
var config,
data;
var draw = function() {
Backend.getData(function(retrievedData) {
// data retrieved asynchronously, store them:
data = retrievedData;
// to generate the list, we need both config and data:
if (!config) {
// <---- How can I wait until config has been loaded?
}
$scope.list = generateList(config, data);
}
// let's say, Backend.getConfig would make an asynch call and fetch the config data
Backend.getConfig(function(retrievedConfig) {
// config data retrieved asynchronously, store them:
config= retrievedConfig;
});
}
I hope you get the point: to generate the list and assign it to the scope, both input data is needed. But since the config does not change in the short run, I don't want to use $q.all() to wait for both. So, the config data should only be fetched the first time and then held in the controller (referenced by the variable "config"). This is implemented in the code above, but if somebody fires the 'draw()' function and the config is still loading, how can I make the code "wait" until the necessary config data has been fetched?
$emit/$broadcast come to my mind, but I don't like the idea because it feels like the wrong weapon for this target.
Are there other possibilities?
I would wrap the Backend.getConfig() call with another service that executes the call only when needed and returns a promise. The other times, it returns a promise resolved with a cached value. E.g.:
.service("getCachedConfig", function(Backend, $q) {
var cachedConfig;
return function getCachedConfig(cb) {
var deferred = $q.defer();
deferred.promise.then(cb); // install the callback, to adhere to your API; I would consider using promises all the way...
if( cachedConfig != null ) {
deferred.resolve(cachedConfig);
}
else {
Backend.getConfig(function(retrievedConfig) {
cachedConfig = retrievedConfig;
deferred.resolve(cachedConfig);
});
}
return deferred.promise;
};
})
Now you can do $q.all(Backend.getPromiseForData(...), getCachedConfig()) (assuming the method Backend.getPromiseForData returns a promise). The config will be fetched only once. You can trivially enhance this to add a cache expiration.