Reputation: 3299
I have a function that looks like this:
async function convertExamples(data,path,urlParameters) {
urlParameters='h'
for (var k in data) {
if (typeof data[k] == "object" && data[k] !== null)
return await convertExamples(data[k], path+'.'+k, urlParameters);
else {
return urlParameters + path+'.'+k + '=' + data[k] + '&';
}
}
return urlParameters;
}
as you can see it's recursive (it runs itself on line 4).
What it's doing is just looping through a nested object to get the values at the end, and add them to a url string.
let urlParameters = await convertExamples(data,'gui','?');
console.log('finished url parameters:',urlParameters);
Right now it doesn't seem to work because urlParameters is still equal to ? at the end.
How can I make it so the program waits for the convertExamples()
function to complete (including every instance run from within itself), before continuing?
I tried adding await
before calling it, but nothing seems to change it.
If possible, I'd prefer to avoid promises.
Upvotes: 1
Views: 4451
Reputation: 135425
Chris's post teaches you that this can be a purely synchronous function. I think you should go a step further and separate the concerns of the program.
We can start with a object flattening function, flatten
. This allows us to work with an arbitrarily nested object in a linear way. Most importantly, it is generic which means it can be reused any time you need this behavior -
function* flatten (t, path = [])
{ switch (t?.constructor)
{ case Object:
case Array:
for (const [k,v] of Object.entries(t))
yield *flatten(v, [...path, k])
break
default:
yield [path, t]
}
}
const data = {
a: {
b: 'foo',
c: 'bar',
},
d: 'baz',
};
for (const [path, value] of flatten(data))
console.log(JSON.stringify(path), value)
["a","b"] foo
["a","c"] bar
["d"] baz
Next we will write objectToParams
which is a simple wrapper around our flatten
. Notice how the caller can handle the path
however it is suitable for their need. And instead of hobbling URL components together by hand using string concatenation, we will use URLSearchParams. This is a much safer API and ties in directly with the URL api -
function objectToParams (t, base)
{ const p = new URLSearchParams()
for (const [path, value] of flatten(t, base ? [ base ] : []))
p.set(path.join("."), value)
return p.toString()
}
console.log(objectToParams(data))
console.log(objectToParams(data, "gui"))
Expand the snippet below to verify the result in your own browser -
function* flatten (t, path = [])
{ switch (t?.constructor)
{ case Object:
case Array:
for (const [k,v] of Object.entries(t))
yield *flatten(t[k], [...path, k])
break
default:
yield [path, t]
}
}
function objectToParams (t, base)
{ const p = new URLSearchParams()
for (const [path, value] of flatten(t, base ? [ base ] : []))
p.set(path.join("."), value)
return p.toString()
}
const data = {
a: {
b: 'foo',
c: 'bar',
},
d: 'baz',
};
console.log(objectToParams(data))
console.log(objectToParams(data, "gui"))
a.b=foo&a.c=bar&d=baz
gui.a.b=foo&gui.a.c=bar&gui.d=baz
Upvotes: 1
Reputation: 7298
You're confused about the difference between async programming and recursive functions. You need to "wait" for an async process to finish with promise resolutions, callbacks, or async/await. But this function is synchronous, despite being recursive. You're not getting the answer you expect because you're not returning any values. You don't have to do anything extra to wait for it to finish. Getting the output of '?' means it's finished.
Here's a corrected version of your function. It tracks the path as it recurses, but it doesn't need the third argument.
function convertExamples(data, path) {
let urlParameters = '';
for (var k in data) {
if (typeof data[k] == "object" && data[k] !== null)
urlParameters += convertExamples(data[k], path+'.'+k);
else
urlParameters += path+'.'+k + '=' + data[k] + '&';
}
return urlParameters;
}
const data = {
a: {
b: 'foo',
c: 'bar',
},
d: 'baz',
};
const result = '?' + convertExamples(data,'gui')
console.log('finished url parameters:',result);
// finished url parameters: ?gui.a.b=foo&gui.a.c=bar&gui.d=baz&
EDIT
So what's the difference? Your recursive function keeps the JavaScript process busy the whole time. A function calling itself is no different than a chain of different functions calling each other in this respect. JavaScript doesn't and can't do anything else until all those executions are done. That's synchronous.
Asynchronous programming is when JavaScript registers a callback (i.e. code to execute) to be triggered by an event in the future and then stops. The "waiting" you're thinking about is really about "how do I make sure JavaScript wakes up and starts executing again when that event happens"? The event could be a network call returning, or a user clicking their mouse, or a timeout that you set finally matching the clock. As I implied before, callbacks, promises, and async/await are all just sugar around how we queue up code to run in the future on some event.
Upvotes: 3
Reputation: 321
Strings are primitive and immutable in javascript, so you can't modify them - only replace. The urlParameters += ...
line actually creates a local copy of the urlParameters
and works on it, instead of modifying the original. You can return the string from every function call:
function convertExamples(data,path,urlParameters) {
for (var k in data) {
if (typeof data[k] == "object" && data[k] !== null)
urlParameters = convertExamples(data[k], path+'.'+k, urlParameters);
else
urlParameters += path+'.'+k + '=' + data[k] + '&';
}
return urlParameters;
}
...
let urlParameters = '?';
urlParameters = convertExamples(data,'gui',urlParameters)
Or better yet, use a modifiable datatype, such as an array, and parse it to a string at the end:
function convertExamples(data,path,paramsArr) {
for (var k in data) {
if (typeof data[k] == "object" && data[k] !== null)
convertExamples(data[k], path+'.'+k, paramsArr);
else
paramsArr.push(path+'.'+k + '=' + data[k]);
}
}
...
const paramsArr = [];
convertExamples(data,'gui',paramsArr);
const urlParameters = '?' + paramsArr.join('&');
Upvotes: 1