Async Callback Execution After setTimeout Problem - javascript

In my react app, I have a function that somewhat simulates a call to an API. Is there a way I can synchronously run a callback function after this API request so that my data comes back in the order I sent it?
// the order in the array here matters
const wordTypeArr = ['type1', 'type2', 'type3']
// loop sequentially through array
wordTypeArr.forEach((v,i) => {
getRandomWordFromAPI(v, addWord)
})
//simulated "API" - can't modify this function
const getRandomWordFromAPI = (type, callback) => {
return setTimeout(function() {
callback(
type in dictionary ?
sample(dictionary[type]) :
null
);
}, (Math.random() * 750 + 250));
}
//callback runs after response - update the state
const addWord = (val) => {
const newState = wordList
newState.push(val)
setWordList(newState);
}
As you can see, the getRandomWordFromAPI function returns a timeout function then executes the callback after the timeout asynchronously (out of order). This is undesirable, as my results must be in order.
Maybe I need to wrap addWord in a promise? or something similar?

Changing that function would indeed be preferred as it'll make the code simpler to read. But since you can send getRandomWordFromAPI a callback, you can send the resolve function as the callback instead of addWord() and then chain the addWord to the resolution.
I've put some parts of your code in comments, since we don't have the dictionary object and such, but the structure stays the same.
// the order in the array here matters
const wordTypeArr = ['type1', 'type2', 'type3'];
//simulated "API" - can't modify this function
const getRandomWordFromAPI = (type, callback) => {
return setTimeout(function() {
callback( type );
/*
callback(
type in dictionary ?
sample(dictionary[type]) :
null
);*/
}, (Math.random() * 750 + 250));
}
//callback runs after response - update the state
const addWord = (val) => {
console.log( val );
/*
const newState = wordList
newState.push(val)
setWordList(newState);
*/
}
const randomWords = wordTypeArr.map( word => {
return new Promise(( resolve, reject ) => {
getRandomWordFromAPI( word, resolve );
});
});
Promise.all( randomWords ).then( words => words.forEach( addWord ));

It's 2019, promises are in, callbacks are out. On a more serious note, here's how you can refactor your code to make it work the way you want to:
// mock the API
const getRandomWordFromAPI = (type, callback) => {
setTimeout(() => {
let word = `some-word-of-type-${type}`;
callback(word);
}, 1000)
}
// promisify the mocked API
const getRandomWordFromAPIPromise = (type) => new Promise(resolve => {
getRandomWordFromAPI(type, resolve);
});
// fetch all data asynchronously, in order
const wordTypeArr = ['type1', 'type2', 'type3'];
const promises = wordTypeArr.map(getRandomWordFromAPIPromise);
Promise.all(promises).then(words => {
// do whatever you want with the words
console.log(words)
});

Related

How to build an object tree with recursive API calls?

I want to construct a tree where each node is used in a API call to get the children nodes; starting at the root. And this will be done recursively until it reaches the TREE_DEPTH_LIMIT
export const search = async (searchTerm) => {
try {
const tree = {};
await createTree(searchTerm, tree);
return tree;
} catch (err: any) {}
};
const TREE_DEPTH_LIMIT = 3;
const createTree = async (searchTerm, tree) => {
if (counter === TREE_DEPTH_LIMIT) {
counter = 0;
return;
}
counter++;
tree[searchTerm] = {};
try {
const res = await axiosInstance.get(
`/query?term=${searchTerm}`
);
// res.data.terms is an array of strings
res.data.terms.forEach((term) => {
createTree(term, tree[searchTerm]);
});
} catch (err) {}
};
I am trying to do this recursively in the createTree() function above. It will use the searchTerm in the API call. Then it will loop through res.data.terms and call createTree() on each on the terms. But the output is not what I was expecting.
This is the output:
const tree = {
apple: {
apple_tree: {},
tree: {},
},
};
The expected output: (because the TREE_DEPTH_LIMIT is 3, it should have 3 levels in the tree)
const tree = {
apple: {
apple_tree: {
leaf: {},
},
tree: {
trunk: {},
},
},
};
Or is my solution completely incorrect and I should be going for another approach??
Some issues:
counter seems to be a global variable, but that will not work out well as at each return from recursion, counter should have its value restored. It is better to use a local variable for that, so that every execution context has its own version for it. Even better is to make it a parameter and let it count down instead of up.
The recursive call is not awaited, so in search the promise returned by createTree may resolve before all the children have been populated, and so you would work with an incomplete tree.
Not a real problem, but it is not the most elegant that the caller must create a tree object and pass it as argument. I would redesign the functions so that search will create that object itself, and then use a recursive function to create children -- so I'd name that function createChildren, instead of createTree.
Here is a snippet that first mocks the get method so you can actually run it:
// Mock for this demo
const axiosInstance = {async get(term) {const delay = ms => new Promise(resolve => setTimeout(resolve, ms));await delay(50);return {data: {terms: {"apple": ["apple_tree", "tree"],"apple_tree": ["leaf"],"leaf": [],"tree": ["trunk"],"trunk": []}[term.split("=")[1]]}};}}
const createChildren = async (searchTerm, depth) => {
if (depth-- <= 0) return {};
try {
const res = await axiosInstance.get(`/query?term=${searchTerm}`);
const promises = res.data.terms.map(async (term) =>
[term, await createChildren(term, depth)]
);
return Object.fromEntries(await Promise.all(promises));
} catch (err) {
console.log(err);
}
};
const TREE_DEPTH_LIMIT = 3;
const search = async (searchTerm, depth=TREE_DEPTH_LIMIT) =>
({[searchTerm]: await createChildren(searchTerm, depth)});
// Demo
search("apple").then(tree =>
console.log(tree)
);

Promisify a JavaScript callback function

I've been reading everything I can about async/await and Promises, but I can't quite get it to work. I keep getting 'Promise ' and when it's fulfilled I receive 'Undefined'.
I think I've spent about two days on this! I'm not sure what's wrong. Here's the un-promisified version.
const getCoords = (address) => {
geocoder.addressSearch(address, (result) => {
return result;
}
);
};
And here is my attempt at the promised code:
const getCoords = async (address) => {
await geocoder.addressSearch(address, async (result) => {
return result;
}
);
};
I'm just trying to return the result. If anyone could steer me in the right direction it would be really appreciated.
Both of your examples are incorrect
using continuation-passing style
In this style, the continuation ("callback") ignores any return values as this function is run asynchronously from the main call -
const geocoder =
{ addressSearch: (address, callback) =>
setTimeout(callback, 1000, {
coords: [12.345, 67.890]
})
}
const getCoords = (address, callback) =>
geocoder.addressSearch(address, result =>
callback(result.coords)
)
getCoords("foobar", console.log)
// [ 12.345, 67.89]
[ 12.345, 67.89]
using promises
In this style you do not specify a continuation. Instead a Promise is returned which represents the value of a future computation. You can await a promise in an async function in order to retrieve the result, or chain a .then handler -
const geocoder =
{ addressSearch: (address, callback) =>
setTimeout(callback, 1000, {
coords: [12.345, 67.890]
})
}
const getCoords = (address) =>
new Promise(resolve =>
geocoder.addressSearch(address, result =>
resolve(result.coords)
)
)
async function main() {
const coords = await getCoords("foobar")
console.log(coords)
return "done"
}
main().then(console.log, console.error)
[ 12.345, 67.89]
"done"

React typescript Promise with webservice and depending another function in loop

On button click I am looping through record id's pass that 1 by 1 to webservice which will return XML data where in I will get ID of another record which I will pass to another webservice which will return result success. After finish this I want to show message success
Function defination
const GetRunningWorkflowTasksForCurrentUserForListItemRequest = (
absoluteUrl: string,
itemId: number,
listName: string,
callback
) => {
const soapURL = `${absoluteUrl}/example.asmx?op=GetListItem`
const soapRequest = `SOAP Request`
getWFData(soapURL, soapRequest, callback) // Get XML with call back function parameter where we will process data
}
Second Function Call
const getWFData = (soapURL: string, soapRequest: string, callback) => {
const xmlhttp = new XMLHttpRequest()
xmlhttp.open("POST", soapURL, true)
xmlhttp.onreadystatechange = () => {
if (xmlhttp.readyState === 4) {
if (xmlhttp.status === 200) {
callback(xmlhttp.responseText)
}
}
}
xmlhttp.setRequestHeader("Content-Type", "text/xml")
xmlhttp.send(soapRequest)
}
First Function Call with loop
const approveSelected = (ids: number[]) => {
ids.forEach((val, idx) => {
const absoluteUrl = props.context.pageContext.web.absoluteUrl
// First function
GetRunningWorkflowTasksForCurrentUserForListItemRequest(
absoluteUrl,
val,
"Temp",
GetRunningWorkflowTasksForCurrentUserForListItemResponse //XML Response
)
})
}
Third Function where we got XML response
const GetRunningWorkflowTasksForCurrentUserForListItemResponse = (response: any) => {
const parser = require("fast-xml-parser")
const absoluteUrl = props.context.pageContext.web.absoluteUrl
if (parser.validate(response) === true) {
const jSONObj = parser.parse(response)
const spTaskId =
jSONObj["soap:Envelope"]["soap:Body"].GetRunningWorkflowTasksForCurrentUserForListItemResponse
.GetRunningWorkflowTasksForCurrentUserForListItemResult.UserTask.SharePointTaskId
processFlexiTaskRequest2(
absoluteUrl,
"Approve",
spTaskId,
"Workflow Tasks",
processFlexiTaskResponse2Response, //XML Response function
""
)
}
}
Forth and Final call for inside loop
const processFlexiTaskResponse2Response = (response: any) => {
const parser = require("fast-xml-parser")
if (parser.validate(response) === true) {
const jSONObj = parser.parse(response)
const result =
jSONObj["soap:Envelope"]["soap:Body"].ProcessFlexiTaskResponse2Response.ProcessFlexiTaskResponse2Result
}
}
I am really confuse, How can I make chain with promise and show confirm once loop finish. Please help
Two key steps are required to make this work with promises:
Convert each id to a promise that either resolves to the respective response or alternatively to the result of processFlexiTaskResponse2Response
Use Promise.all() to combine all these promises to one promise that resolves when all of the per-id promises are resolved.
This is the most relevant part
const approveSelected = (ids: number[]) => {
const promises = ids.map((val, idx) => {
const absoluteUrl = props.context.pageContext.web.absoluteUrl
// First function
return GetRunningWorkflowTasksForCurrentUserForListItemRequest(
absoluteUrl,
val,
"Temp"
).then(response => {
// transform response via
// GetRunningWorkflowTasksForCurrentUserForListItemResponse
// and
// processFlexiTaskResponse2Response
});
});
Promise.all(promises).then(results => {
// the code here executes once all results are there.
});
}
You have to change GetRunningWorkflowTasksForCurrentUserForListItemResponse and
processFlexiTaskResponse2Response to simply return their respective result so you can chain them.
To make GetRunningWorkflowTasksForCurrentUserForListItemRequest return a promise you can change it like this:
const GetRunningWorkflowTasksForCurrentUserForListItemRequest = (
absoluteUrl: string,
itemId: number,
listName: string,
callback
) => new Promise((resolve, reject) => {
const soapURL = `${absoluteUrl}/example.asmx?op=GetListItem`
const soapRequest = `SOAP Request`
getWFData(soapURL, soapRequest, resolve);
});

Recursive function in Redux

Trying to pass a reference to the recursive function to check if Redux action data fetch is complete, but getting function reference errors
const fetchAccountComplete = (state, accountNo) => { //state here is function reference
return new Promise(resolve => {
(function waitForFetchComplete(state, accountNo) {
const {isFetching, receivedAt} = state().account[accountNo] // getting state not a function here
if (!isFetching) return resolve()
setTimeout(waitForFetchComplete, 100)
})()
})
}
Is there a better way to return a promise to the caller function in Redux dispatch actions so that once the data is fetched, i need to do some other logic in other action.
Update 1:
should have been more clearer. There are two callers of this Request, Recieve actions on say Account data. First caller is directed similar to the above comment by you so waits until complete, second caller would not be doing the async call again and need to check if data fetch is complete so trying to see if recursive function with check on state so that promise can be resolved is being done
You could take advantage of promising chaining.
Example:
Have three actions like: IS_FETCHING, FETCH_SUCCESS, FETCH_ERROR.
IS_FETCHING:
Will simply set your state as pending (may be useful for showing a loading animation, for example).
FETCH_SUCCESS:
Will contain the result of the fetch to update the state. Will also clear the isUpdating flag
FETCH_ERROR:
Will contain any possible error due to the fetch (application or network error). Will also clear the isUpdating flag
Then, what you could do at application level is:
dispatch({type: IS_FETCHING, payload: data});
fetch(`https://MY-SERVER.com/?data=${data}`)
.then(response => response.json())
.then(json =>
dispatch({
type: isError(json) ? FETCH_RESULT : FETCH_ERROR,
payload: json
})
);
You could even benefit of action creators for the job.
Here is a good guide for that: https://redux.js.org/advanced/async-actions
If you have a function that returns a promise that is called multiple times with the same arguments then you can group that in a way so that the function is not called when it still has an unresolved promise and something tries to call it again with the same arguments.
Here is an example:
//group promise returning function
const createGroup = (cache) => (
fn,
getKey = (...x) => JSON.stringify(x)
) => (...args) => {
const key = getKey(args);
let result = cache.get(key);
if (result) {
return result;
}
//no cache
result = Promise.resolve(fn.apply(null, args)).then(
(r) => {
cache.done(key); //tell cache promise is done
return r;
},
(e) => {
cache.done(key); //tell cache promise is done
return Promise.reject(e);
}
);
cache.set(key, result);
return result;
};
//creates a cache that will remove cached value when
// Promise is done (resolved or rejected)
const createCache = (cache = new Map()) => {
return {
get: (key) => cache.get(key),
set: (key, value) => cache.set(key, value),
done: (key) => cache.delete(key),
};
};
//function that retuns a promise
const later = (time, value) => {
console.log('executing later with values', time, value);
return new Promise((r) =>
setTimeout(() => r(value), time)
);
};
//create group function with a cache that will remove
// cache key when promise is resolved or rejected
const groupAndRemoveCacheOnDone = createGroup(
createCache()
);
//grouped version of the later function
const groupedLater = groupAndRemoveCacheOnDone(later);
//testing the groped later
groupedLater(100, 8); //first call causes console.log
groupedLater(100, 8); //same arguments will not call later
groupedLater(100, 8); //will not call later
//will call later because arguments are not the same
// as the other calls
groupedLater(100, 'XX');
groupedLater(100, 8) //will not call later
.then((value) => {
console.log('resolved with:', value);
//this will call later because cache value is removed
// after promise is resolved
return groupedLater(100, 8);
})
.then(() => {
//testing with fetchAccountComplete
console.log(
'***** how many times is fetchAccountComplete called *****'
);
const fetchAccountComplete = (state, accountNo) => {
console.log(
'fetchAccountComplete called with',
accountNo
);
return new Promise((resolve) => {
(function waitForFetchComplete(state, accountNo) {
const {
isFetching,
receivedAt,
} = state().account[accountNo]; // getting state not a function here
if (!isFetching) return resolve();
setTimeout(
() => waitForFetchComplete(state, accountNo),
100
);
})(state, accountNo);
});
};
const data = {
account: [{ isFetching: true }],
};
const state = () => data;
const groupedFetchAccountComplete = groupAndRemoveCacheOnDone(
fetchAccountComplete
);
groupedFetchAccountComplete(state, 0);
groupedFetchAccountComplete(state, 0);
groupedFetchAccountComplete(state, 0);
groupedFetchAccountComplete(state, 0).then((resolve) =>
console.log('resolved')
);
data.account[0].isFetching = false;
});

How to wait for multiple graphql mutations to finish then call another function

Basically I have an array of objects and based from that array's length, I need to do a mutation for each instance like so:
arrayOfObjects.forEach((object) => {
someGraphQlMutation({
variables: object.id,
});
});
-----------------
const [someGraphQlMutation] = useMutation(
SOME_GRAPHQL_MUTATION,
{
onCompleted: (data) => {
// callback
}
}
);
However, after all the mutations are done, I need to call another function. I need to wait for the iteration mutations to finish before calling the callback function. Am I able to use Promise.all in this context?
Yes, you can use Promise.all to await all the mutations:
const [someGraphQlMutation] = useMutation(SOME_GRAPHQL_MUTATION);
const doEverything = async () => {
try {
const results = await Promise.all(arrayOfObjects.map(object => {
return someGraphQlMutation({ variables: object.id });
}));
// results will be an array of execution result objects (i.e. { data, error })
// Do whatever here with the results
} catch (e) {
// Handle any errors as appropriate
}
}

Categories