I'm trying to build an object based on the responses from multiple promises, but I'm noticed where only the first promise is actually being worked on while the second is being ignored. What is the best practice to make this work?
if (goldenListKeys[0].name === 'date') {
const date = moment('07-01-2019', 'YYYY-MM-DD').format('MM/DD/YYYY');
_.assign(tmpObj, { inputData: { [goldenListKeys[0].name]: date } });
try {
await this.plansApi
.compileFields({ tmpObj, carrier, benefitQuery })
.catch(error => {
value = error.response['invalid-selection'];
console.log(`One: ${value}`);
});
} catch (err) {}
}
if (goldenListKeys[1].name === 'state') {
console.log('Here');
_.assign(tmpObj, {
inputData: { ...tmpObj, [goldenListKeys[1].name]: 'NC' },
});
try {
await this.plansApi
.compileFields({ tmpObj, carrier, benefitQuery })
.catch(error => {
value = error.response['invalid-selection'];
_.assign(goldenListKeys, { filler: value });
console.log(`Two: ${value}`);
});
} catch (err) {}
}
It appears that you missed one of the fundamental features of async/await.
An async function can contain an await expression that pauses the execution of the async function and waits for the passed Promise's resolution, and then resumes the async function's execution and returns the resolved value.
The key part being is that the execution inside the function call is paused. So your next if statement won't be considered until after the first promise has resolved.
from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
This is usually where I like to use the Promise.all function MDN Link
This is how I would modify your code (I know it doesn't use async/await but, it should accomplish your goal)
function yourFunction(){
let myPromiseArray = [];
if (goldenListKeys[0].name === 'date') {
const date = moment('07-01-2019', 'YYYY-MM-DD').format('MM/DD/YYYY');
_.assign(tmpObj, { inputData: { [goldenListKeys[0].name]: date } });
myPromiseArray.push(his.plansApi
.compileFields({ tmpObj, carrier, benefitQuery }))
}
if (goldenListKeys[1].name === 'state') {
_.assign(tmpObj, {
inputData: { ...tmpObj, [goldenListKeys[1].name]: 'NC' },
});
myPromiseArray.push(this.plansApi
.compileFields({ tmpObj, carrier, benefitQuery }))
}
Promise.all(myPromiseArray).then((resultArray)=>{
//do something with results
}).catch(errorArray => {
//handle Errors
})
}
Related
I connect to a Postgres database in Node and I get a return value from the database. However, when I try to pass that value to another function, the value is undefined. Check out this code:
async function doDbCall(queryString) {
await client.connect()
.then(async () => {
await client.query(queryString)
.then(dbRes => {
console.log("dbDoCall - dbRes", dbRes.rows[0]);
return dbRes;
})
});
}
Here is the calling function:
async function testTheDb() {
try {
const myReturnVal = await dbConn.doDbCall("SELECT NOW() as now");
console.log("db val:", myReturnVal);
}
catch(ex) {
console.log("db err", ex.message);
}
}
In the console.log in doDbCall() on the query.then success, I get a value in dbRes.rows[0]. The console.log shows dbDoCall - dbRes { now: 2022-09-26T16:47:14.465Z }. So I return that value.
However, in the testTheDb() function, I have another console log where I log the value of myReturnVal. The result of that log shows db val: undefined.
My question is this: Why is myReturnVal null in testTheDb()?
You should not mix async/await syntax with .then() calls. Your problem is that neither the doDbCall function nor the async () => {…} callback do return anything, your only return keyword is nested inside a nested then callback.
You should write either
function doDbCall(queryString) {
return client.connect()
.then(() => {
return client.query(queryString);
})
.then(dbRes => {
console.log("dbDoCall - dbRes", dbRes.rows[0]);
return dbRes;
})
.finally(() => {
client.end();
});
}
or
async function doDbCall(queryString) {
try {
await client.connect()
const dbRes = await client.query(queryString);
console.log("dbDoCall - dbRes", dbRes.rows[0]);
return dbRes;
} finally {
client.end();
}
}
I have the following logic for reading and writing redis state
export async function updateRedis() {
let stateName = 'stateName'
try {
let isSuccess = false
while (!isSuccess) {
try {
await redis
.watch(stateName, function (err) {
if (err) {
logger.error(`Error in watch state: ${err}`)
}
redis.get(stateName, function (err, result) {
if (err) {
logger.error(`Error in get state: ${err}`)
}
let state = JSON.parse(result)
// do some processing
redis.multi()
.set(stateName, JSON.stringify(state))
.exec(function (err, result) {
if (err) {
logger.error(`Error in set state: ${err}`)
}
if (result != null) {
isSuccess = true
}
})
console.log(`isSuccess for ${stateName} `, isSuccess)
})
})
} catch (e) {
logger.error(`Error: ${e}`)
}
}
} catch (e) {
logger.error(`Error: ${e}`)
}
return Promise.resolve(true)
}
This will print out
"isSuccess for stateName false"
"isSuccess for stateName true"
"isSuccess for stateName true"
So after the flag changes to true, it will continue for more loops. Sometimes it does more than just once.
Am I doing something wrong?
You cannot mix a synchronous loop (while (!isSuccess) { ... }) with asynchronous functions (redis.watch(stateName, function (err) { ... })).
You can also not await callback-based asynchronous functions. A function must return a promise to be awaitable. Since node-redis gives you promises when you don't pass callbacks to its methods, the key is not to do that (redis.watch(stateName, function (err) { ... }) → redis.watch(stateName)).
Your approach needs to be redone.
Let's make a function that encapsulates a redis transaction with optimistic locking. It takes a connection object, a key, and a value-transforming function, and it returns the result of the .set() operation:
const redisTransaction = async (client, key, transformer) => {
// https://github.com/redis/node-redis/blob/master/docs/isolated-execution.md
return client.executeIsolated(async isolatedClient => {
await isolatedClient.watch(key);
const val = await isolatedClient.get(key);
return isolatedClient.multi()
.set(key, await transformer.call(isolatedClient, val))
.exec();
});
};
Now you can await this function, because it returns a promise. That means we can make a simple infinite loop that exits immediately in case of success (via return), or retries indefinitely.
export async function updateRedis(key, transformer) {
while (true) {
try {
return await redisTransaction(redis, key, transformer);
} catch (err) {
logger.error(`Error for state ${key}: ${err}`);
}
}
}
A transformer function takes a value, and returns a new value. Inside it, the this keyword refers to the isolatedClient from the transaction, which could be useful if your transformation depends on other values from that client.
const result = await updateRedis('stateName', async function (val) {
const state = JSON.parse(val);
const newState = await modifyStateSomehow(state);
return JSON.stringify(newState);
});
The modifyStateSomehow() can itself be an asynchronous (i.e. "promise-returning") function. If it's not, you can make the state transformer a regular function by removing the async and the await.
I get problems with async/await functions and changing state in React.
This is my async function, which is triggered by clicking on the button:
async startNewEntry() {
this.addIssue();
let issue_id;
console.log(this.state.timeEntry, "started_timeEntry")
if (this.state.timeEntry?.issue?.id) {
issue_id = this.state.timeEntry?.issue?.id;
} else {
issue_id = (await this.issueService.list()).data[0]?.id;
}
const { data } = await this.timeEntryService.create({
comments: this.state.timeEntry.comments,
issue_id,
spent_on: moment(new Date()).format("YYYY-MM-DD"),
hours: 0.01,
activity_id: this.localStorageService.get("defaultActivityId")
});
In this function I use this.addIssue, which use this.createIssue, which changing my class component state:
addIssue() {
this.projectService.list([]).then(response => {
response.data = response.data.filter((x: any) => x.status === 1);
this.setState(
{
activeProjects: response.data
},
() => {
this.createIssue();
}
);
});
}
createIssue() {
this.issueAddService
.create({
project_id: this.state.activeProjects[0].id,
tracker_id: trakerId,
priority_id: priorityId,
subject: this.state.timeEntry.comments,
description: this.state.timeEntry.comments
})
.then(response => {
let timeEntry = this.state.timeEntry;
timeEntry.issue = response.data;
this.setState({
timeEntry
});
})
.catch(error => {
console.log("error", error);
});
}
As you can see, in my async function I new to have my new State, but actually async function works before my this.addIssue function. I know that question might be little freaky, but Thanks in forward!!
I am not a React expert, but I don't fully understand why there are lot of setState invocations spread around the place.
If you leave the setState to the end of the function, then you might not need to worry about correctly sequencing asynchronous calls to it (although the other answer does show how this can be achieved).
Perhaps invoking it once might make the code clearer. I welcome corrections...
async startNewEntry() {
const activeProjects = await fetchActiveProjects()
const issue = await this.createIssue()
const timeEntry = await createTimeEntry({ issue, comments: this.state.timeEntry.comments })
this.setState({ activeProjects, issue, timeEntry })
}
async fetchActiveProjects() {
const { data } = await this.projectService.list([])
return data.filter(({ status }) => status === 1)
}
async createIssue() {
const { data } = await this.issueAddService.create({
project_id: this.state.activeProjects[0].id,
tracker_id: trakerId,
priority_id: priorityId,
subject: this.state.timeEntry.comments,
description: this.state.timeEntry.comments
})
return data
}
async createTimeEntry({issue, comments}) {
const { data } = await this.timeEntryService.create({
comments,
issue_id: issue?.id || (await this.issueService.list()).data[0]?.id,
spent_on: moment(new Date()).format("YYYY-MM-DD"),
hours: 0.01,
activity_id: this.localStorageService.get("defaultActivityId")
})
return data
}
You can probably speed this up further by parallelizing the first two async calls:
async startNewEntry() {
const [activeProjects, issue] =
await Promise.all([fetchActiveProjects(), this.createIssue()])
const timeEntry = await createTimeEntry({ issue, comments: this.state.timeEntry.comments })
this.setState({ activeProjects, issue, timeEntry })
}
If you want startNewEntry to wait to do its work until after addIssue has done its work, you need to:
Have addIssue return a promise it fulfills when it's finished its work, and
Use await when calling it: await this.addIssue();
If you need startNewEntry to see the updated state, addIssue's promise will need to be fulfilled from the state completion handler callback, like this:
addIssue() {
// *** Return the promise chain to the caller
return this.projectService.list([]).then(response => {
response.data = response.data.filter((x: any) => x.status === 1);
// *** Create a new promise
return new Promise(resolve => {
this.setState(
{
activeProjects: response.data
},
() => {
this.createIssue();
resolve(); // *** Fulfill the promise
}
);
});
});
}
Often, new Promise is an anti-pattern, particularly when you have another promise you can chain from. But in this case, since you need to wait for the callback from setState (which isn't promise-enabled), it's appropriate. (
Note my comment on the question. I think you're setting up an endless loop...
Below I'm attempting to assign a value, an array, to a local variable. I'm calling a function that returns an array to get that value. The problem is that the Promises in the 2nd function aren't resolved until after the array has been returned to the caller. I've tried using Promise.all() on retArray but it never works for me. When I console.log() out my someobject object the arrOfTitles field never prints out because the call to SkywalkerTitles() returns an empty array.
You can run the code here.
So how do I get someObject.arrOfTitles to get the array of titles from SkywalkerTitles()?
function SkywalkerTitles(){
let retArray = [];
fetch('https://swapi.co/api/people/')
.then(function(response){
response.json()
.then(function(result){
return result.results[0];
})
.then(function(result){
result.films.forEach(function(film){
fetch(film)
.then(function(response){
response.json().then(function(result){
console.log(result.title);
retArray.push(result.title);
});
});
})
.catch(function(error){
console.log(error)
});
});
})
.catch(function(error){
console.log(error)
});
}
function UseReturnedArray() {
let someObject = { aThing: '', anotherThing: '', arrOfTitles: null };
someObject.aThing = 'Thing One';
someObject.anotherThing = 'Thing Two';
someObject.arrOfTitles = SkywalkerTitles();
console.log('Object With Returned Array:\n\n', JSON.stringify(someObject, null, 2));
}
UseReturnedArray();
As much as #Svenskunganka answer is complete and worthy, alternative if your environment doesn't yet support async/await AND you don't want to use a transpiler - seeing as you're already partially familiar with Promises, this code shouldn't look as foreign :p
Your main problem is that SkywalkerTitles doesn't actually return anything (you claim it returns an empty array, it actually has no return statement, therefore the returned value is undefined
I've also removed the .catch code, because where you had it would actually cause issues, in that your code handles rejections, yet further down the chain the code would expect that the data was actually valid! Only a single catch almost always ever needed
function SkywalkerTitles() {
return fetch('https://swapi.co/api/people/').then(function (response) {
return response.json();
}).then(function (result) {
return result.results[0];
}).then(function (result) {
return Promise.all(result.films.map(function (film) {
return fetch(film).then(function (response) {
return response.json();
}).then(function (result) {
return result.title;
});
}));
});
}
function UseReturnedArray() {
SkywalkerTitles().then(function (arrOfTitles) {
var someObject = {
aThing: '',
anotherThing: '',
arrOfTitles: arrOfTitles
};
console.log('Object With Returned Array:\n\n', JSON.stringify(someObject, null, 2));
}).catch(function(reason) {
console.log(reason);
});
}
UseReturnedArray();
Note how the promise chain is flattened compared to yours, and use of Array#map instead of Array#forEach + Array#push
You should take advantage of async/await, which could really clean up your code quite a bit and make it more understandable:
async function SkywalkerTitles () {
let character = await fetch('https://swapi.co/api/people/').then(res => res.json()).then(res => res.results[0])
return await Promise.all(character.films.map(async (film) => {
return await fetch(film).then(res => res.json()).then(res => res.title)
}))
}
async function UseRetrunedArray () {
try {
let someObject = {
aThing: '',
anotherThing: '',
arrOfTitles: await SkywalkerTitles()
}
console.log(someObject)
} catch (e) {
console.error(e)
}
}
UseRetrunedArray()
See https://repl.it/Lc2f/2
If this looks alien to you, I suggest you read a bit into how async/await and Promise work together.
Well, I am lost in await and async hell. The code below is supposed to loop through a list of files, check if they exist and return back the ones that do exist. But I am getting a zero length list.
Node V8 code: caller:
await this.sourceList()
if (this.paths.length == 0) {
this.abort = true
return
}
Called Functions: (I took out stuff not relevant)
const testPath = util.promisify(fs.access)
class FMEjob {
constructor(root, inFiles, layerType, ticket) {
this.paths = []
this.config = global.app.settings.config
this.sourcePath = this.config.SourcePath
}
async sourceList() {
return await Promise.all(this.files.map(async (f) => {
let source = path.join(this.sourcePath, f.path)
return async () => {
if (await checkFile(source)) {
this.paths.push(source)
}
}
}))
}
async checkFile(path) {
let result = true
try {
await testPath(path, fs.constants.R_OK)
}
catch (err) {
this.errors++
result = false
logger.addLog('info', 'FMEjob.checkFile(): File Missing Error: %s', err.path)
}
return result
}
Your sourceList function is really weird. It returns a promise for an array of asynchronous functions, but it never calls those. Drop the arrow function wrapper.
Also I recommend to never mutate instance properties inside async methods, that'll cause insane bugs when multiple methods are executed concurrently.
this.paths = await this.sourceList()
if (this.abort = (this.paths.length == 0)) {
return
}
async sourceList() {
let paths = []
await Promise.all(this.files.map(async (f) => {
const source = path.join(this.sourcePath, f.path)
// no function here, no return here!
if (await this.checkFile(source)) {
paths.push(source)
}
}))
return paths
}
async checkFile(path) {
try {
await testPath(path, fs.constants.R_OK)
return true
} catch (err) {
logger.addLog('info', 'FMEjob.checkFile(): File Missing Error: %s', err.path)
this.errors++ // questionable as well - better let `sourceList` count these
}
return false
}