keep looping a function until the callback inside resolves to true - javascript

I've the two interconnected functions.
module.exports = async function newRunner() {
console.log('No Config found. Requesting new Runner ID');
if (!process.env.ADDRESS) {
const a = await inquirer.prompt([
{
type: 'input',
message: 'Enter the runner api address',
name: 'address',
},
]);
config.ADDRESS = a.address;
} else {
config.ADDRESS = process.env.ADDRESS;
}
config.save();
const res = await auth.checkAddress();
console.log('New Runner ID', res.id);
config.ID = res.id;
config.TOKEN = res.token;
config.save();
};
The line const res = await auth.newRunner(); call the following function.
exports.checkAddress = args => new Promise((resolve, reject) => {
const cArgs = {
data: args,
path: {},
parameters: {},
headers: {
'Content-Type': 'application/json',
},
};
client.post(`${config.ADDRESS}/new-runner`, cArgs, (data, response) => {
if (response.statusCode !== 200) return reject(new Error(`Status code ${response.statusCode}`));
return resolve(data);
})
.on('responseTimeout', (res) => {
console.log('Update Response Timeout!', res);
reject(new Error('response has expired'));
})
.on('error', (err) => {
reject(err);
});
});
I need to keep the first function running until the address entered is correct and the second function resolves to true. I've tried the following.
await newRunner();
while (!await auth.newRunner()) {
await newRunner();
}
The functions runs as it should if I enter the correct address, wrong address breaks it, but I want it to keep looping until the address is correct. What do I need to change do archive the correct behaviour?

Your "main" newRunner (at the top of your question) only fulfills when auth.newRunner fulfills, and rejects otherwise. auth.newRunner only fulfills when it successfully got data. So to loop until you've successfully got data, you need to wrap the call in a try/catch:
let failed;
do {
try {
await newRunner();
failed = false;
} catch {
failed = true;
}
} while (failed);
Might be best to put some limits on that, though, so it can't cycle infinitely.

Related

Resolve is returned before for loop is completed inside a Promise

I am very confused to why the the return resolve({ status: true, data: newIDs }); is called before the for loop has finished.
Code
createPallet: (data) => {
return new Promise(async (resolve, reject) => {
const newIDs = [];
try {
for (const record of data) {
mysqlConnection.beginTransaction();
mysqlConnection.query(
"INSERT INTO ...",
[record.BatchId, record.PalletNumber, record.PrimaryWeightId],
async (error, results) => {
if (error) {
return reject(error);
}
// Create pallet sku record
await PalletSkusService.createPalletSku(
...
);
console.log(results.insertId);
newIDs.push(results.insertId);
}
);
}
return resolve({ status: true, data: newIDs });
} catch (error) {
mysqlConnection.rollback();
return reject(error);
}
});
},
Expectation
I expect the for loop to get all the new inserted id's and store them into newIDs array. Once that is done, I can return a resolve with the data.
However, this part I don't quite understand is - why does the resolve is ran before the for loop has finished?
What is the correct approach in this situation?
mysqlConnection.query is returning immediately and not waiting for the data to return. I don't know what library you're using for this but you want to find the promise based method to accomplish this. Then, modify the loop so it waits for an array of promises to complete before resolving.
async createPallet(data) {
try {
// modified loop
const newIDs = await Promise.all(data.map((record) => {
mysqlConnection.beginTransaction();
// this is a best guess for how the a promise-style query would look
const await result = await mysqlConnection.query(
"INSERT INTO ...",
[record.BatchId, record.PalletNumber, record.PrimaryWeightId]
);
// Create pallet sku record
await PalletSkusService.createPalletSku(
...
);
console.log(results.insertId);
return results.insertId;
}));
return { status: true, data: newIDs };
} catch (error) {
mysqlConnection.rollback();
throw error;
}
},

javascript async/await catch block trapping error instead of mocha

There is a mocha test as follows:
it('(t4) should assert that issues a GET request by john should fetch his own records via svc1', done => {
let filter = { where: { key: 'john' }};
let testHelperUrl = `${svc1}/api/TestHelper?filter=${encodeURIComponent(JSON.stringify(filter))}`;
let fetchToken = cb => {
req('GET', testHelperUrl, (err, resp) => {
if(err) {
return cb(err);
}
cb(null, resp.data[0]);
});
};
let getWithToken = ({ value: token }, cb) => {
let consumerUrl = `${svc1}/api/Consumers?access_token=${token}`;
req('GET', consumerUrl, (err, resp) => {
if(err) {
return cb(err);
}
cb(null, resp.data);
})
};
async.seq(fetchToken, getWithToken)((err, data) => {
if(err) {
return done(err);
}
expect(data.length).to.equal(2);
done();
});
});
It calls some APIs via the req(), and, it is defined as follows:
const req = (method, url, data, cb) => {
if (method.toLowerCase() === 'get') {
if (typeof cb === 'undefined') {
cb = data;
data = null;
}
(async () => {
try {
let response = await got(url, { responseType: 'json', https: { rejectUnauthorized: false } });
cb(null, { code: response.statusCode, data: response.body });
} catch (err) {
cb(err);
}
})();
}
else {
(async () => {
try {
let response = await got.post(url, { json: data, responseType: 'json', https: { rejectUnauthorized: false } });
cb(null, { code: response.statusCode, data: response.body });
} catch (err) {
cb(err);
}
})();
}
};
The above code uses got for doing http requests. Its design is promised-based, and hence, the reason to why async/await pattern is used here.
I am also using the async library here. The following line: expect(data.length).to.equal(2) should fail, and, the expectation is that mocha should report that error correctly.
However, the error is actually trapped by the async library. It should report an error saying callback already was called. But instead the program just dies. However, through debugging I am able to confirm that it was an error trapped in the async module. No idea why it dies abruptly though.
Am I doing something wrong? Or should I modify mocha code to properly handle the assertion error like wrapping that in a try/catch block or something?
Update:
Solution is to invoke the callback function to req() inside of a process.nextTick(). E.g.
try {
let response = await got(url, { responseType: 'json', https: { rejectUnauthorized: false } });
process.nextTick(() => cb(null, { code: response.statusCode, data: response.body }));
} catch (err) {
process.nextTick(() => cb(err));
}

How to add a new then after fetch has run

I have a method that runs a fetch request and then saves the result or error like this:
saveTema() {
this.gateway.editTema(this.state.tema)
.then(tema => {
this.setState({
tema,
error: null,
isDirty: false,
});
})
.catch(httpOrOtherError => {
if (httpOrOtherError.status) {
if (httpOrOtherError.status === 400) {
httpOrOtherError.json().then(result => {
const serverValidationfailures =
this.transformValideringsfeil(result.valideringsfeil);
this.setState({
error: {
valideringsfeil: {...serverValidationfailures},
},
showActivationDialog: false,
})
});
} else {
this.setState({
error: {httpError: {status: httpOrOtherError.status, statusText: httpOrOtherError.statusText}},
showActivationDialog: false,
});
}
} else {
this.setState({
error: {fetchReject: {message: httpOrOtherError.message}},
showActivationDialog: false,
})
}
})
}
And this is the fetch request itself:
editTema(tema) {
return fetch(
this.temaUrl(tema.id),
{
method: 'PUT',
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(tema)
})
.then(res => {
if (res.ok) {
return res.json();
}
throw res;
}
);
}
I would like to run this method from another one, and check if everything went ok with this method and based on that do further actions. Something like this:
this.saveTema().then(() => {
this.props.history.push({
pathname: '/tema',
state: {
successMessage: `Tema ${this.state.tema.id} ble oppdatert`,
}
}}));
But, this is of course wrong, I am not sure how can I do this, to run some code after the fetch handling of the fetch request has finished. What is the right way to do it?
saveTema() {
return this.gateway.editTema(this.state.tema)
...
Return the promise and then you'll be able to do exactly what you are trying to do.
Return the editThema result after setting up the handlers:
saveTema() {
let prom = this.gateway.editTema(this.state.tema)
prom.then(tema => {
// .. success handling code
})
.catch(httpOrOtherError => {
// .. error handling code
})
return prom;
}
Now you can call your function exactly like you wanted to.
You can achieve that by two approaches
Using async/await
Using native Promise
1. async/await way
userController.js
const userUtils = require('./userUtils');
const userCtr = {};
userCtr.searchUser = async (req, res) => {
try {
const { userName } = req.query;
const result = await userUtils.searchUser(userName);
return res.status(200).json(result);
} catch (err) {
return res.status(err.code).json({ error: err.error });
}
};
module.exports = userCtr;
userUtils.js
const userUtils = {};
userUtils.searchUser = async (userName) => {
try {
if (userName) {
// ...Do some cool stuff
const result = [];
return result;
}
const errorObj = { code: 400, error: 'ERR_VALID_PARAM' };
throw errorObj;
} catch (err) {
console.error(err);
throw err;
}
};
module.exports = userUtils;
2. Promise way
userController.js
const userUtils = require('./userUtils');
const userCtr = {};
userCtr.searchUser = (req, res) => {
const { userName } = req.query;
userUtils.searchUser(userName)
.then((result) => {
return res.status(200).json(result);
})
.catch((err) => {
return res.status(err.code).json({ error: err.error });
});
};
module.exports = userCtr;
userUtils.js
const userUtils = {};
userUtils.searchUser = (userName) => {
return new Promise((resolve, reject) => {
if (userName) {
// ...Do some cool stuff
const result = [];
return resolve(result);
} else {
const error = { code: 400, error: 'Please provide valid data!' }
return reject(error);
}
});
};
module.exports = userUtils;
In both approaches you can hold further execution (in both approach Promise are used directly or indirectly), In a second approach you can achieve by .then().catch() whereas in the first approach just you need to put a keyword await and put async on your function, I suggest you to use async/await. Because when you need to wait for the completion of more than 3 promises and yo go with Native Promise then your code will be so messy like .then().then().then() Whereas in a first approach you just need to put a keyword await on starting of your function, Using async/await approach your code will neat and clean and easily understandable and easy to debug.

AWS Cognito returning undefined error async/await

My question is very similar to this one as I am attempting to wrap the authenticateUser SDK method in a Promise and resolve/reject that.
Code:
async function cognitoAuth(credentials, next, res) {
const userData = {
Username: credentials.email,
Pool: userPool
};
const authenticationDetails = getAuthenticationDetails(credentials);
let userCredentials;
let authenticatedUserResponse;
cognitoUser = new CognitoUser(userData);
try {
userCredentials = await checkIfAuthenticated(credentials.email);
if (userCredentials.UserStatus === CognitoUserStatus.FORCE_CHANGE_PASSWORD) {
res
.status(203)
.send(userCredentials);
} else if (userCredentials.UserStatus === CognitoUserStatus.CONFIRMED) {
authenticatedUserResponse = await authenticateUser(authenticationDetails);
console.log(authenticatedUserResponse);
}
} catch(err) {
if (err.message === CognitoErrorMessages.USER_NOT_EXIST) {
next({
name: err.message,
status: 404
});
}
}
}
As you can see, I have 2 awaited functions (checkIfAuthenticated and authenticateUser). If checkIfAuthenticated throws a an error and rejects the promise, then the catch is fine with a valid err object.
However, if authenticateUser throws an error, the catch is invoked but err is undefined.
This is my authenticateUser:
function authenticateUser(authenticationDetails) {
return new Promise((resolve, reject) => {
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: (result) => {
resolve(merge(err, {
res: "SUCCESS"
}));
},
onFailure: (err) => {
reject(merge(err, {
status: 401
}));
},
newPasswordRequired: (userAttrs, requiredAttrs) => {
resolve(merge(userAttrs, {
res: "NEW_PASS_REQ"
}));
}
});
});
}
Using breakpoints, the onFailure gets invoked and it is the correct err object so am unsure as to why it is undefined in the catch
Figured it out. One of my colleagues pointed out that I running babel over my server code by having this in my index.js (my entry point for the server):
require("#babel/polyfill");
require("#babel/register");
A transpilation issue in Node. As soon as I took that out and switched to ES5 imports/exports it worked fine

Figuring the complexity of Promise.all

I have been struggling for quite some time to get this multiple async nodejs request apis to work but unfortunately i am not able to get them work.
Index.js Code:
service.get(
"/restraunts",
versionRoutes({
"1.0.0": getRestrauntsList
})
);
function getRestrauntsList(req, res, next) {
console.log("Started getRestrauntsList");
file1
.appEnvironment(req, res, next)
.then(function(result) {
return file2.getRestrauntsList(req, res, next);
})
.then(function(result) {
res.status(200).send(result);
return;
})
.catch(function(errorResult) {
res.status(500).send(errorResult);
return;
});
}
File2.js
module.exports = {
getRestrauntsList: function(req, res, next) {
console.log("getRestrauntsList started..");
var cities = [1, 2, 3, 4, 5];
let restrauntsList = [];
let urlArray = [];
var restrauntsListPromise = cities.map(function(id) {
return new Promise(function(resolve, reject) {
var options = {
method: "GET",
url: "someurl/" + id + "/restaurants",
headers: {
"AUTH-TOKEN": "TOKEN"
}
};
request(options, function(error, response, body) {
if (error) {
if ("message" in error) {
errorMsg = error.message;
var result = {
status: "error",
message: errorMsg
};
} else {
var result = {
status: "error",
message: "Resource Timeout."
};
}
reject(result);
return promise;
}
console.log(
"Response: " + JSON.stringify(response)
);
if (response.statusCode === 200 || response.statusCode === 201) {
body = JSON.parse(body);
if (body.success) {
let result = {
status: "success",
data: body.result
};
resolve(result);
} else {
let result = {
status: "error",
message: body.error
};
reject(result);
}
} else {
let result = {
status: "error",
message: body.error
};
reject(result);
}
});
});
});
console.log('restrauntsListPromise:' + JSON.stringify(restrauntsListPromise));
Promise.all(restrauntsListPromise).then(function(result) {
var content = result.map(function(restraunts) {
return restrauntsList.push(restraunts.body);
});
// res.send(content);
resolve({
restrauntsList: restrauntsList
});
return promise;
});
},
};
Ideally i expect to get the response of all the apis in the
restrauntsListPromise
and then using Promise.all i should iterate all the promises and formulate my required object.
The response of my code however is
restrauntsListPromise:[{},{},{},{},{}]
and then
Response: {"statusCode":200,"body":"{\"success\":true,\"res
Response: {"statusCode":200,"body":"{\"success\":true,\"res
Response: {"statusCode":200,"body":"{\"success\":true,\"res
Response: {"statusCode":200,"body":"{\"success\":true,\"res
Response: {"statusCode":200,"body":"{\"success\":true,\"res
Ideally what should happen is i should be able to pass the combined result of all the five apis calls as a single object back to the calling promise here
.then(function(result) {
res.status(200).send(result);
return;
})
The problem being the method getRestrauntsList finishes execution and then after some time, i get the responses of the apis.
The problem being the method getRestrauntsList finishes execution and then after some time, i get the responses of the apis.
This is because you're not returning a promise from the getRestrauntsList().
There are few items that needs to addressed to make it work
1. Remove the unused variables
return promise; // both inside promise.all[] and request()
There is no declared variable named promise. So, you can remove it.
2. Accessing .body instead of .data
You're resolving as resolve({status: "success", data: body.result}); But When you are iterating, you are accessing using .body instead of .data. You need to be using .data. Also, you can eliminate restrauntsList array since you're using a .map()
3. Calling resolve() to return values.
You can't use resolve() to return value within Promise.all[] since you didn't create a promise using new Promise((resolve, reject) => { ... });. By default, a return within a promise will be a promise. so, a simple return will suffice. But if you want to be explicit, you can also return using Promise.resolve()
Making those changes,
return Promise.all(restrauntsListPromise).then(function (result) {
return {
restrauntsList: result.map(function (restraunts) {
return restraunts.data;
})
};
//or using Promise.resolve();
// return Promise.resolve({
// restrauntsList: result.map(function (restraunts) {
// return restraunts.data;
// })
// });
});
You are looking for
return Promise.all(restrauntsListPromise).then(function(result) { /*
^^^^^^ */
var contents = result.map(function(restaurants) {
return restaurants.body;
// ^^^^^^^^^^^^^^^^^^^^^^^^
});
return {restaurantsList: contents};
// ^^^^^^
});
You need to return the promise chain from the getRestrauntsList method, you should return the value from the map callback instead of using push on an array, and you will need to return from the then callback - there is no resolve function as you're not inside a new Promise constructor that you only need for callback APIs.

Categories