JavaScript: Promise.all returning undefined - javascript

I'm trying to create a user account creation script with a focus on unique usernames - a prefix and a suffix from a pool, a list of existing usernames, and a list of reserved usernames.
That's just the start of it (no saving yet!), and already that would require three connections, so I just decided to see if I can code a function that would handle them all.
Here's my code so far - and it's on AWS Lambda, and tested via API Gateway, if that means anything:
const dbConnMysql = require('./dbController');
var methods = {
createUser: function() {
let getPrefixSuffixList = new Promise((resolve, reject) => {
let connection = dbConnMysql.createConnection();
dbConnMysql.startConnection(connection)
.then((fulfilled) => {
let table = 'userNamePool';
return dbConnMysql.selectFrom(connection, table, '*', null);
})
.then((fulfilled) => {
console.log(fulfilled);
return dbConnMysql.closeConnection(connection)
.then((fulfilled) => {
resolve(fulfilled);
});
})
.catch((error) => {
console.log(error);
reject(error);
});
});
let getTempUserNameList = new Promise((resolve, reject) => {
// Same as getPrefixSuffixList, different table
});
let getRealUserNameList = new Promise((resolve, reject) => {
// Same as getPrefixSuffixList, different table
});
return new Promise((resolve, reject) => {
Promise.all([getPrefixSuffixList, getTempUserNameList, getRealUserNameList])
.then((fulfilled) => {
console.log(fulfilled[0]);
console.log(fulfilled[1]);
console.log(fulfilled[2]);
let response = {
"statusCode": 200,
"headers": {"my_header": "my_value"},
"body": {"Prefix Suffix":fulfilled[0], "Temp UserName List":fulfilled[1], "Real UserName List":fulfilled[2]},
"isBase64Encoded": false
};
resolve(response);
})
.catch((error) => {
let response = {
"statusCode": 404,
"headers": {"my_header": "my_value"},
"body": JSON.stringify(error),
"isBase64Encoded": false
};
reject(response);
})
});
}
};
module.exports = methods;
This function is called elsewhere, from index.js:
app.get('/createUserName', function (req, res) {
var prom = Register.createUser();
prom.then((message) => {
res.status(201).json(message);
})
.catch((message) => {
res.status(400).json(message);
});
})
Now I'm not entirely sure if what I did with the Promise.All is correct, but from what little I know, if one promise fails, the Promise.All fails.
However, the individual promises do work just fine, and log out the respective results from the database. But inside the Promise.All, it all just logs out undefined.
Is there something I'm missing?

The cause of your problem is this. You need to run the functions, these then return the promise that will eventually resolve:
Promise.all([getPrefixSuffixList(), getTempUserNameList(), getRealUserNameList()])
Here is some simpler code as well. In general there is no need for new Promise(). This code may fix other issues. Also, the undefined could be being printed from any part of the code, make sure it's being printed where you think it is.
// Dummy MySQL connector
const dbConnMysql = {
createConnection: () => 'Connection',
startConnection: conn => new Promise(resolve => setTimeout(resolve, 100)),
selectFrom: (conn, t, q, n) =>
new Promise(resolve =>
setTimeout(() => {
console.log(`${conn}: SELECT ${q} FROM ${t}`);
resolve(`x ${t} RECORDS`);
}, 100)
),
closeConnection: conn => new Promise(resolve => setTimeout(resolve, 100)),
};
const methods = {
createUser() {
const getPrefixSuffixList = () => {
const connection = dbConnMysql.createConnection();
return dbConnMysql
.startConnection(connection)
.then(() => {
const table = 'userNamePool';
return dbConnMysql.selectFrom(connection, table, '*', null);
})
.then(fulfilled => {
console.log(fulfilled);
return dbConnMysql.closeConnection(connection).then(() => fulfilled);
})
.catch(error => {
console.log(error);
// Note: this catch will stop the error from propagating
// higher, it could also be the cause of your problem.
// It's okay to catch, but if you want the error to
// propagate further throw a new error here. Like this:
throw new Error(error);
});
};
const getTempUserNameList = () => {
// Same as getPrefixSuffixList, different table
};
const getRealUserNameList = () => {
// Same as getPrefixSuffixList, different table
};
return Promise.all([getPrefixSuffixList(), getTempUserNameList(), getRealUserNameList()])
.then(fulfilled => {
console.log('fulfilled[0]: ', fulfilled[0]);
console.log('fulfilled[1]: ', fulfilled[1]);
console.log('fulfilled[2]: ', fulfilled[2]);
return {
statusCode: 200,
headers: { my_header: 'my_value' },
body: {
'Prefix Suffix': fulfilled[0],
'Temp UserName List': fulfilled[1],
'Real UserName List': fulfilled[2],
},
isBase64Encoded: false,
};
})
.catch(error => ({
statusCode: 404,
headers: { my_header: 'my_value' },
body: JSON.stringify(error),
isBase64Encoded: false,
}));
},
};
methods.createUser();

Related

Limit calls to external api node

I'm, in node and I have an array of obj {suggestion: 'text', rank: '2'} that I want to use to make a call to bing to get the first result on each of them.
At the moment, I have managed it using a Promise.all
await Promise.all(suggestions.map(async (s, i) => await bingWebSearch(s.suggestion.replace(/\s/g, '+'), i)))
.then(r => {
suggestions.map((s, i) => console.log(`
n${i+1}. ${s.suggestion} | times suggested: ${s.rank} | url: ${s.webpage}
`))
})
.catch(e => e.message)
that will call the function bingWebSearch and assign the website URL to the obj
const bingWebSearch = async (query, i) => {
return await axios.get('https://api.bing.microsoft.com/v7.0/search?', {
headers: {
'Ocp-Apim-Subscription-Key': SUBSCRIPTION_KEY
},
params: {
count: 1,
mkt: 'en-US',
q: query
}
}).then(r => {
if (r.data.webPages) return suggestions[i].webpage = r.data.webPages.value[0].url
}).catch(e => console.log(e.message))
}
So basically, this will fire 30 calls to bing, but I am allowed only to do 3/second how I can I achieve it? I have tried with a setTimeOut, but using the async func is a bit tricky, so it did not work.
here is my suggestion:
function delay(ms) {
return new Promise(function (resolve) {
setTimeout(resolve, ms);
});
}
const bingWebSearch = (query, ms) => {
return new Promise((resolve, reject) => {
delay(ms).then(() => {
axios
.get("https://api.bing.microsoft.com/v7.0/search?", {
headers: {
"Ocp-Apim-Subscription-Key": SUBSCRIPTION_KEY
},
params: {
count: 1,
mkt: "en-US",
q: query
}
})
.then(r => {
resolve(r.data.webPages.value[0].url);
})
.catch(e => {
console.log(e.message);
// this will just return an empty string as a result, if something goes wrong
resolve("");
// you can also reject and handle the exception inside calling for loop
// if more advanced error handling is required
// reject(e);
});
});
});
};
async function load() {
const requests = suggestions.map((s, i) => {
// delay each request by 400ms to avoid hitting the limit of 3 requests per second
const ms = i * 400;
return bingWebSearch(s.suggestion.replace(/\s/g, "+"), ms);
});
const res = await Promise.all(requests);
suggestions.forEach((s, i) => {
s.webpage = res[i];
console.log(`n${i + 1}. ${s.suggestion} | times suggested: ${s.rank} | url: ${s.webpage}`);
});
}
(function () {
load();
})();
I refactored bingWebSearch a bit, to only return the result, and not directly changing the list of suggestions. Try keeping functions as self contained as possible without external dependencies.

How to make two api calls using Promise.all within Angular9?

I making an api call using Promise.all as below:
Promise.all(this.hostName.slice(0, this.Id.length).map((hostName) => {
return this.serviceC.status(hostName)
.then(res => {
return new Promise((resolve, reject) => {
const oretry: ORInterface = {
oQid: res.rows[0].qid,
reason: this.reason
};
this.serviceB.retry(oretry).subscribe(resolve);
});
});
}))
.then(() => {
this.dialog.close();
})
.catch(err => {
console.log(err);
});
The above code is working fine.
Now I want to make another api call after the successful completion of this.serviceB.retry(oretry).
The second api is this.serviceB.createDbEntry(sentry) and sentry looks as below:
const sretry: SDInterface = {
hostName,
Id: this.Id.slice(0, this.Id.length),
reason: this.reason
};
And, I am doing it as below
Promise.all(this.hostName.slice(0, this.Id.length).map((hostName) => {
return this.serviceC.status(hostName)
.then(res => {
return new Promise((resolve, reject) => {
const oretry: ORInterface = {
oQid: res.rows[0].qid,
reason: this.reason
};
const sretry: SDInterface = {
hostName,
Id: this.Id.slice(0, this.Id.length),
reason: this.reason
};
this.serviceB.retry(oretry).subscribe(resolve);
this.serviceB.createDbEntry(sentry).subscribe(resolve);
});
});
}))
.then(() => {
this.dialog.close();
})
.catch(err => {
console.log(err);
});
The above code is giving an error:
error: "SequelizeValidationError: string violation: Id cannot be an array or an object"
It is looks like it is not calling the second api for every Id
You may want to take a look a forkJoin
import { Observable, forkJoin } from 'rxjs';
And then
ngOnInit() {
let one = this.http.get('some/api/1') //some observable;
let two = this.http.get('some/api/2') // another observable;
forkJoin([one, tow]).subscribe(response => {
// results[0] is our one call
// results[1] is our second call
let var1 = response[1];
let var2 = response[0];
}/*, error => { in case error handler } */);
}
Wouldn't it be better to use Promise.all() once more?
Promise.all(this.hostName.slice(0, this.Id.length).map((hostName) => {
return this.serviceC.status(hostName)
.then(res => {
return new Promise((resolve, reject) => {
const oretry: ORInterface = {
oQid: res.rows[0].qid,
reason: this.reason
};
this.serviceB.retry(oretry).subscribe(resolve);
});
})
.then(() => {
return Promise.all(this.Id.slice(0, this.Id.length).map(id => {
return new Promise((resolve, reject) => {
const sretry: SDInterface = {
hostName,
Id: id,
reason: this.reason
};
this.serviceB.createDbEntry(sentry).subscribe(resolve);
});
})
});
}))
.then(() => {
this.dialog.close();
})
.catch(err => {
console.log(err);
});
And using toPromise() will make the code more concise.
Promise.all(this.hostName.slice(0, this.Id.length).map((hostName) => {
return this.serviceC.status(hostName)
.then(res => {
const oretry: ORInterface = {
oQid: res.rows[0].qid,
reason: this.reason
};
return this.serviceB.retry(oretry).toPromise();
})
.then(() => {
return Promise.all(this.Id.slice(0, this.Id.length).map(id => {
const sretry: SDInterface = {
hostName,
Id: id,
reason: this.reason
};
this.serviceB.createDbEntry(sentry).toPromise();
})
});
}))
.then(() => {
this.dialog.close();
})
.catch(err => {
console.log(err);
});
Use combineLatest, in Angular we use RxJs not promises.
combineLatest(
[this.http.get('call1'), this.http.get('call2')]
).subscribe(([result1, result2]) => {
// do stuff with result1 and result2
});
promise.all takes input in an array and gives response in an array,
Create 2 functions each with your asynchronous logic returning a promise,
Say funcA and funcB, then use below to invoke them parellely
Promise.all([funcA(this.hostName), funcB(this.id)])
.then(respones => {
console.log(responses[0]); //return value for funcA
console.log(responses[1]); //return value for funcB
})
.catch(err => console.log(err));
I am assuming your logic of functions are correct, I just copy-pasted from your question and gave them structure
const funcA = (hostName) => {
hostName.slice(0, this.Id.length).map((hostName) => {
return this.serviceC.status(hostName)
.then(res => {
return new Promise((resolve, reject) => {
const oretry: ORInterface = {
oQid: res.rows[0].qid,
reason: this.reason
};
this.serviceB.retry(oretry).subscribe(resolve);
});
});
});
}
const funcB = (Id) => {
Id.slice(0, this.Id.length).map(id => {
return new Promise((resolve, reject) => {
const sretry: SDInterface = {
hostName,
Id: id,
reason: this.reason
};
this.serviceB.createDbEntry(sentry).subscribe(resolve);
});
})
}

Trying to refactor a promisified function in to try-catch block

I am trying to refactor this code using try-catch blocks:
export const authorizeConnectyCube = async (accessToken) => {
const userCredentials = {
provider: 'firebase_phone',
'firebase_phone[project_id]': "xxxxxxxx",
'firebase_phone[access_token]': accessToken,
};
await createSession();
return new Promise((resolve, reject) => {
ConnectyCube.login(userCredentials, (error, user) => {
user ? resolve(user) : reject(error);
})
}).catch(error => console.log(error));
}
const createSession = () => {
return new Promise((resolve, reject) => {
ConnectyCube.createSession((error, session) => {
session ? resolve(session.user) : reject(error)
})
}).catch(error => console.log(error));
}
However I'm not getting the same result - the asynchronousity seems to be being handled differently. Here is my attempt at refactoring:
export const authorizeConnectyCube = async (accessToken) => {
const userCredentials = {
provider: 'firebase_phone',
'firebase_phone[project_id]': "xxxxxxxxxx",
'firebase_phone[access_token]': accessToken,
};
await createSession();
try {
ConnectyCube.login(userCredentials, (error, user) => {
return user;
})
}
catch (error) {
console.log(error)
}
}
const createSession = () => {
try {
ConnectyCube.createSession((error, session) => {
return session.user
})
} catch (error) {
console.log(error);
}
}
Is there any particular part of what I'm wrong? Thanks.
Callback-based APIs don't readily turn into something you can use for async/await (which under the hood uses promises). You'll have to "promisify" them first (i.e. wrap them in promises).
Here's an example of what I'm trying to say:
// Promisify these callback-based APIs.
const login = userCredentials => {
return new Promise((resolve, reject) => {
ConnectyCube.login(userCredentials, (error, user) => {
user ? resolve(user) : reject(error);
})
})
})
const createSession = () => {
return new Promise((resolve, reject) => {
ConnectyCube.createSession((error, session) => {
session ? resolve(session.user) : reject(error)
})
})
})
// Then use them in an async function
export const authorizeConnectyCube = async (accessToken) => {
const userCredentials = {
provider: 'firebase_phone',
'firebase_phone[project_id]': "xxxxxxxx",
'firebase_phone[access_token]': accessToken,
}
try {
await createSession()
return login(userCredentials)
} catch (e) {
console.warn(e)
}
}
Also, async functions return promises, with the resolved value being the return value, and the rejected value being any uncaught error thrown inside. A value wrapped in a promise as return value for an async function is redundant.
If you're using Node 8+, it has a utility called promisify which accepts a callback-based API and returns a promise-returning version of it.

How to call a function after the other function is done?

My problem is that i coded a simple overstock function for my game-items trading site (site working with socket.io!). And when the site is loading, two functions getting user and site inventories with items and listing them into two boxes, but the user inv loads faster and info about overstock comes later as the bot inv is loaded. I need to execute the "get user inventory" function after the "get bot inventory" function is fully done. I tried to set a sleep function between them, but it worked only for the main function that loading whole functions. In other words sleep(2000) delays loading both inventories when it set between this two functions.
sorry for my bad english :-D
Aaand there is my code (i left only important part):
io.on('connection', (socket) => {
var userObject = false
if (
typeof socket.handshake.session.passport !== 'undefined' &&
typeof socket.handshake.session.passport.user !== 'undefined' &&
typeof socket.handshake.session.passport.user.id !== 'undefined'
) {
userObject = socket.handshake.session.passport.user
}
socket.emit('site', config.site)
socket.emit('user', userObject)
socket.on('get bot inv', (id) => {
Trade.getInventory(config.bots[id].steamID64, config.appID, config.contextID, (err, data) => {
socket.emit('bot inv', { error: err, items: data })
})
})
socket.on('get user inv', (steamID64) => {
Trade.getInventory(steamID64, config.appID, config.contextID, (err, data) => {
socket.emit('user inv', { error: err, items: data })
})
})
socket.on('get bots inv', () => {
const params = []
Object.keys(config.bots).forEach((index) => {
const bot = config.bots[index]
params.push({
id: index,
steamID64: bot.steamID64,
appID: config.appID,
contextID: config.contextID,
})
})
Trade.getInventories(params, (data) => {
socket.emit('bots inv', data)
socket.emit('bots floats', Trade.getFloatValues())
})
})
})
Look into promises.
var promise1 = new Promsie((resolve, reject) => {
socket.on('get bot inv', (id) => {
Trade.getInventory(config.bots[id].steamID64, config.appID, config.contextID, (err, data) => {
socket.emit('bot inv', { error: err, items: data })
resolve();
})
})
})
var promise2 = new Promsie((resolve, reject) => {
socket.on('get user inv', (steamID64) => {
Trade.getInventory(steamID64, config.appID, config.contextID, (err, data) => {
socket.emit('user inv', { error: err, items: data })
resolve();
})
})
})
If you want to wait until one finishes
promise1.then(() => {return promise2});
If you want N things to execute and await all of them, use the following
Promise.all([promise1, promise2]).then(() => {execute something else});
For further documentation:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
You might want to use promises. At first, encapsulate the socket listeners and the API call with promises:
const getUserId = new Promise(res => socket.on('get bot inv', res));
const getBotId = new Promise(res => socket.on('get bot inv', res));
function getInventory(id) {
return new Promise((res, rej) => {
Trade.getInventory(id, config.appID, config.contextID, (err, data) => {
if(err) rej(err) else res(data);
});
}
Then its easy to chain the promises:
(async function() {
const userID = await getUserId;
const data = await getInventory(userID);
socket.emit({ data });
const botID = await getBotId;
const botData = await getInventory(config.bots[botID].steamID64);
socket.emit({ botData });
})();

Promise continues after error

I have some async code that needs to stop in case of error but keeps executing:
async saveCoupons({ state, rootState, dispatch, commit }) {
const promises = []
state.userCoupons.forEach(coupon => {
if (coupon.isNew && coupon.isUpdated) {
// if the user is creating a new coupon
promises.push(Vue.axios.post('/api_producer/coupons.json', coupon, { params: { token: coupon.token } }))
} else if (!coupon.isNew && coupon.isUpdated) {
// if the user is updating the coupon
promises.push(Vue.axios.patch(`api_producer/coupons/${coupon.id}/`, coupon, { params: { token: coupon.token } }))
}
})
try {
await Promise.all(promises)
dispatch('utilities/showModal', 'success', { root: true })
dispatch('fetchProducerCoupons')
} catch (err) {
let couponToken = err.request.responseURL.split('token=')[1]
commit('ADD_ERROR_ON_COUPON', couponToken)
console.log(err)
}
}
This is how the code is currently structured, it works, but I realize it's terrible. What I need to do is stop the excution of
dispatch('utilities/showModal', 'success', { root: true })
dispatch('fetchProducerCoupons')
In case one of the api calls fails. I wanted to catch the error inside the forEach so I already have the item available and I can add the error to it right away as opposed to doing it after (which is what I'm doing now with { params: { token: coupon.token } }.
I think the best way would be to wrap the Vue.axios requests into your own Promise. Then, if the requests fail, you have the coupon tokens in your error.
Something like
const promises = [];
promises.push(
Vue.axios.post('/api_producer/coupons.json', coupon)
.catch(() => { throw new Error(coupon.token) }));
Promise.all(promises).catch(tokens => {
tokens.forEach(token => {
// Commit/handle errorous token
});
});
You can wrap your api call in another promise and check the status. Something like this:
promises.push(
new Promise((resolve, reject) => {
Vue.axios.post('/api_producer/coupons.json', coupon, { params: { token: coupon.token } })
.then((response) => {
if (response.status !== 200) {
coupon.error = true;
reject();
} else {
resolve();
}
});
})
);
The reject will keep these two lines from being executed:
dispatch('utilities/showModal', 'success', { root: true })
dispatch('fetchProducerCoupons')
Thanks to Moritz Schmitz v. Hülst & sklingler93 for the help, I restructured the code and it's working.
I'm wondering if there's a way to write all of this using only async/await... If anybody has an idea, would love to see it :)
saveCoupons({ state, rootState, dispatch, commit }) {
const promises = []
state.userCoupons.forEach(coupon => {
if (coupon.isNew && coupon.isUpdated) {
// if the user is creating a new coupon
promises.push(new Promise((resolve, reject) => {
Vue.axios.post('/api_producer/coupons.json', coupon)
.then(response => resolve(response))
.catch(err => {
reject(err)
commit('ADD_ERROR_ON_COUPON', coupon.token)
})
}))
} else if (!coupon.isNew && coupon.isUpdated) {
// if the user is updating the coupon
promises.push(new Promise((resolve, reject) => {
Vue.axios.patch(`api_producer/coupons/${coupon.id}/`, coupon)
.then(response => resolve(response))
.catch(err => {
reject(err)
commit('ADD_ERROR_ON_COUPON', coupon.token)
})
}))
}
})
Promise.all(promises)
.then(() => {
dispatch('utilities/showModal', 'success', { root: true })
dispatch('fetchProducerCoupons')
})
.catch(err => console.error(err))
},

Categories