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.
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 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 have the following code in Node.js which reads from a file, line by line. I want to do stuff to each line and store it in an array. The array would then be used in other functions in the same file. The problem I'm running into is the async nature of reading the stream which results in an empty array. The solutions I've come across all seem to rely on modules.
function processLine(file) {
const fs = require('fs');
const readline = require('readline');
const input = fs.createReadStream(file);
const rl = readline.createInterface(input);
const arr = []
rl.on('line', (line) => {
// do stuff to data and store in array
})
// return array;
}
I am aware of being able to store the chunks and operate on the whole file with input.on('end', cb)... However, I feel like this would put too much functionality within the cb. Plus I still can't use its return value since its async. I guess my question is, is there a way to store data being read and use it within the file?
If you would like to process elements like chunks - take a look on
highWaterMark
https://nodejs.org/api/stream.html#stream_types_of_streams
Proably you will be instered in:
objectMode
as well.
Also there are interfaces which you could use while use streams:
Readable
Writable
Duplex
Transform
https://nodejs.org/api/stream.html#stream_transform_transform_chunk_encoding_callback
Where you could use any Promise based function and simply use callback to finish processing element at right point of time:
_transform = function(data, encoding, callback) {
this.push(data);
callback();
};
or
https://nodejs.org/api/stream.html#stream_class_stream_transform
_write(chunk, encoding, callback) {
// ...
}
However there is another solution - rxjs binding for node stream - which you could use while process elements.
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