Reputation: 13741
I am building a javascript object where some of the values are defined by asynchronous functions. My problem is the object gets defined faster than the asynchronous functions can return values:
const array = ['a', 'b', 'c', 'd']
const myArrayObj = [];
function returnKey1 () {
// make async call then store it in the return key1val:
return key1val
}
function returnKey2 () {
// make async call then store it in the return key2val:
return key2val
}
function returnKey3 () {
// make async call then store it in the return key3val:
return key3val
}
_.forEach( array, function ( arr ) {
myArrayObj.push({
key1: returnKey1(), // returns undefined
key2: returnKey2(), // returns undefined
key3: returnKey3(), // returns undefined
});
});
Does anyone know the correct way I should be doing this? Thanks in advance!
Upvotes: 0
Views: 6480
Reputation: 35491
The nature of asynchronicity is that you must wait for an async process to finish if you want to access its end result.
In your case, you could achieve this using promises with not a lot of code.:
// promise that resolves after 2 seconds
const timeoutPromise = (str) => new Promise(resolve => setTimeout(() => resolve(str), 2000));
// functions that return promises that will eventually resolve to appropriate key values
function returnKey1() {
return timeoutPromise('key3');
}
function returnKey2() {
return timeoutPromise('key2');
}
function returnKey3() {
return timeoutPromise('key3');
}
// helper function that returns a promise which will resolve with all the keys when all key-returning promises resolve
function getKeys() {
return Promise.all([
returnKey1(),
returnKey2(),
returnKey3()
])
}
// usage
getKeys().then((keys) => {
console.log(
keys[0],
keys[1],
keys[2]
);
});
The old-school approach would be to use callbacks instead of promises, which have larger browser support but are much cruder and primitive.
Note: With modern transpilers and/or promise libraries you can obtain wide browser support for promises as well.
// function that calls its callback after 2 seconds
const timeoutCallback = (cb, key) => setTimeout(() => cb(key), 2000);
// functions that eventually call their callbacks with appropriate key values
function returnKey1(cb) {
return timeoutCallback(cb, 'key1');
}
function returnKey2(cb) {
return timeoutCallback(cb, 'key2');
}
function returnKey3(cb) {
return timeoutCallback(cb, 'key3');
}
// helper function that calls its callback when all the keys are obtained
function getKeys(cb) {
let keys = [undefined, undefined, undefined];
let hasAllKeys = () => keys.every(key => typeof key === 'string');
function makeReturnKeyCallback(idx) {
return (key) => {
keys[idx] = key;
if (hasAllKeys()) {
cb(keys);
}
};
}
returnKey1(makeReturnKeyCallback(0));
returnKey2(makeReturnKeyCallback(1));
returnKey3(makeReturnKeyCallback(2));
}
// usage
getKeys((keys) => {
console.log(
keys[0],
keys[1],
keys[2]
);
});
Upvotes: 2
Reputation:
Whatever you do will end up using promises. You can use Promise.all
directly, or if you are willing to spend a bit more time you could write your own version of Promise.all
for objects; finally, you could do this using async
functions, which are a way to write promise-based code in a way which looks sort of synchronous.
Promise.all
:// Just examples for testing purposes.
function returnKey1() { return promise(); }
function returnKey2() { return promise(); }
function returnKey3() { return promise(); }
// Make a promise returning a random value after random time.
function promise() {
return new Promise(resolve => {
const random = Math.random() * 1000;
setTimeout(() => resolve(random), random);
});
}
// Wait for all promises to finish, then construct result.
Promise.all([returnKey1(), returnKey2(), returnKey3()])
.then(([key1, key2, key3]) => ({key1, key2, key3}))
.then(console.log);
Promise.all
for objectsWe could also create an analog to Promise.all
for objects. Some libraries have such a routine. We will call it promiseAllKeys
. We will pass it an object each of whose property values in a promise; it returns a promise for an object with all the values filled in. This saves us the trouble of converting an array coming back from Promise.all
into the desired object.
function returnKey1() { return promise(); }
function returnKey2() { return promise(); }
function returnKey3() { return promise(); }
function promise() {
return new Promise(resolve => {
const random = Math.random() * 1000;
setTimeout(() => resolve(random), random);
});
}
// Convenience routine to construct an object from arrays of keys and values.
function objectFromKeysAndValues(keys, values) {
const result = {};
for (const i = 0; i < keys.length; i++) result[keys[i]] = values[i];
return result;
}
function promiseAllKeys(promises) {
return Promise.all(Object.values(promises))
.then(values => objectFromKeysAndValues(Object.keys(promises), values));
}
promiseAllKeys({key1: returnKey1(), key2: returnKey2(), key3: returnKey3()})
.then(console.log);
async/await
You could simplify your code using async functions. However, this code as written will wait until each promise completes before running the next one.
function returnKey1() { return promise(); }
function returnKey2() { return promise(); }
function returnKey3() { return promise(); }
function promise() {
return new Promise(resolve => {
const random = Math.random() * 1000;
setTimeout(() => resolve(random), random);
});
}
async function makeObject() {
return {key1: await returnKey1(), key2: await returnKey2(), key3: await returnKey3()};
}
makeObject().then(console.log);
Upvotes: 1
Reputation: 2008
Simple but messy:
const array = ['a', 'b', 'c', 'd']
const myArrayObj = [];
function returnKey1 (cb) {
// make async call then store it in the return key1val:
cb ("key1val");
}
function returnKey2 (cb) {
// make async call then store it in the return key2val:
cb("key2val");
}
function returnKey3 (cb) {
// make async call then store it in the return key3val:
cb("key3val");
}
_.forEach( array, function ( arr ) {
var o ={};
returnKey1(function(key){
o.key1=key;
returnKey2(function(key){
o.key2=key;
returnKey3(function(key){
o.key3=key
myArrayObj.push(obj);
})
})
})
});
});
Upvotes: 0