nested, depended callback functions - javascript

I am quite new to the callback hell (but i understand its meaning more and more now)
The setup:
getAccessToken: call to get an accesstoken from an api
getUserID:with access token, get a userID from an api
getUserDetails: With userID get userdetails from an api
postUserDetails: post retrieveduserdetails to an api
I need to pass values down the calls:
getAccessToken token -> getUserID(token) userID
->getUserDetails(userID) userDetails -> postUserDetails(userDetails)
in my naivity i thought i could get something running like:
postUserDetails(getUserDetails(getUserID(getAccessToken())));
or the other way round (where i would need to change the naming convention but i tried around so much that i ended up entangled in the below
getAccessToken(getUserID(getUserDetails(postUserDetails)))
What is the logical structure to get something like the below with asynchronous ajax calls working? How can I pass down multiple callbacks that get values from the previous call?
Am i relying on any framework (like async) to get a postUserDetails(getUserDetails(getUserID(getAccessToken()))) working?

I need to pass values down the calls in example getAccessToken -> getUserID ->getUserDetails -> postUserDetails I don't know if that was clear from my question
yes, values, but not the promises themselves.
a simple mock of your code:
//your call chain
getAccessToken()
.then(getUserID)
.then(getUserDetails)
.then(postUserDetails)
.then(() => {
console.log("Done");
})
//a mock of your code
//a utility to "simulate" the delay of a server call and return a Promise
function wait(delay) {
return new Promise(resolve => setTimeout(resolve, delay));
}
function getAccessToken() {
console.log("getAccessToken");
//mock the request to the server
return wait(Math.random() * 1000+500)
.then(() => {
console.log("generating a token");
return Math.random().toString(36).slice(2)
});
}
function getUserID(token) {
console.log("getUserID(%o)", token);
//mock the request to the server
return wait(Math.random() * 1000+500)
.then(() => {
console.log("returning userId");
return "id-1234";
});
}
function getUserDetails(userId) {
console.log("getUserDetails(%o)", userId);
//mock the request to the server
return wait(Math.random() * 1000+500)
.then(() => {
console.log("returning user");
return {
id: userId,
firstName: "Zaphod",
lastName: "Beeblebrox"
}
});
}
function postUserDetails(user) {
console.log("postUserDetails(%o)", user);
return wait(Math.random() * 1000+500)
.then(() => {
console.log("userDetails posted");
});
}
.as-console-wrapper{top:0;max-height:100%!important}

Related

Node class start function

I am new to javascript/node and I am building a class that calls the Telegram api every second to get updates and store so I can have other function use that data. I have pieced together code form examples but I am getting an error when I call bot.start(); because no function is being passed to getUpdates. I am not sure why the (fn) is needed.
class Bot {
constructor(token) {
let _baseApiURL = `https://api.telegram.org`;
this.baseApiURL = _baseApiURL;
this.token = token;
}
start(){
this.getUpdates();
}
getBaseApiUrl(){
return this.baseApiURL;
}
getToken(){
return this.token;
}
getAPI(apiName) {
return axios.get(`${this.getApiURL()}/${apiName}`);
}
getApiURL() {
return `https://api.telegram.org/bot${this.getToken()}`;
}
getUpdates(fn) {
this.getAPI('getUpdates')
.then(res => {
this.storeUpdates(res.data);
fn(res.data);
setTimeout(() => {
this.getUpdates(fn);
}, 1000);
})
.catch(err => {
console.log('::: ERROR :::', err);
});
}
storeUpdates(data){
console.log(data);
}
}
const bot = new Bot(TOKEN);
bot.start();
Its not clear what exactly you are trying to achieve with that fn method. You are not passing any method, therefore it fails. This would work
getUpdates() {
this.getAPI('getUpdates')
.then(res => {
this.storeUpdates(res.data);
setTimeout(() => {
this.getUpdates();
}, 1000);
})
.catch(err => {
console.log('::: ERROR :::', err);
});
}
If you want to implement some kind of Observer pattern, you should not couple it with getUpdates method, just create methods for registering observers and notify them when store is changed.
Also the way how you trigger the repetition is not too good, because once you get error (and with HTTP methods you usually get some ERROR sooner or later) it will break the whole flow.
Use something like https://www.npmjs.com/package/cron to trigger periodic actions.

How to correctly string together Promises for synchronous code execution

I posted a similar question several days ago but I have made some changes and commenting on that question was becoming tedious, so it was recommended I ask a new question.
The idea is that I want to execute four equations synchronously. Inside those equations are HTTP requests. I have two of the equations working properly and but there is one equation that involves two POST requests and a GET requests. The second requests relies on the first and the third request relies on the second.
I have tried several different methods to get this to work. I have tried flattening my promises, returning the promises. All kinds of things, with no luck. I am not sure where I am going wrong.
Synchronous code snippet:
this.getData1(user, userID).then(() =>
{
this.getData2(user, userID)
.then(() =>
{
this.getData3(user, lan).then(() =>
{
this.userCheck(user);
})
});
});
I have getData2 and getData3 working.
getData1 looks like:
getData1(user: string, id: string){
console.log('grabbing CC information', id, user);
return new Promise((resolve, reject) =>
{
var status: string;
this._apiService.getAssertion(id).subscribe((data: any) =>
{
let assert = data.toString();
this._apiService.getToken(assert).subscribe((data: any) =>
{
let tkn = data.access_token.toString();
this._apiService.ccMeta(tkn, guid).subscribe((data: any) =>
{
parseString(data, (err, result) =>
{
if (err)
{
throw new Error(err);
}
else
{
status = result['entry']['content'][0]['m:properties'][0]['d:status'][0];
this.ccData.push(
{
key: 'userStatus',
value: status
})
}
});
});
});
});
resolve()
});
}
I also tried something like this previously. It did not work either.
apiService.getAssertion(id).then(assert =>
{
return apiService.getToken(assert.toString(), user);
}).then(data =>
{
return apiService.ccMeta(data.access_token.toString(), id);
}).then(parseStringPromise).then(information =>
{
this.ccData.push(
{
key: 'userStatus',
value: information.entry
});
});
Inside this function the getAssertion function is a POST request. The getToken function is another POST request that relies on the assertion from the first POST request. Finally, ccMeta is a get request that relies on the token from the second POST request.
I would expect getData1 to execute first, then getData2, then getData3, and finally, userCheck. Inside getData1 I need the assertion, then the token, and then get request to execute synchronously. The code snippet above is not executing correctly. The assertion is not properly being used in the getToken equation.
I would greatly appreciate some help.
Since these HTTP calls are in fact observables and not promises, I think you should look into observable composition using pipe and switchMap for instance. If you still want you method to return a promise, it could look like this:
getData1(user: string, id: string) {
console.log('grabbing CC information', id, user);
return new Promise((resolve, reject) => {
this._apiService.getAssertion(id)
.pipe(
switchMap((data: any) => {
let assert = data.toString();
return this._apiService.getToken(assert);
}),
switchMap((data: any) => {
let tkn = data.access_token.toString();
return this._apiService.ccMeta(tkn, guid);
}),
)
.subscribe(
data => {
parseString(data, (err, result) => {
if (err) {
reject(new Error(err));
return;
}
const status: string = result['entry']['content'][0]['m:properties'][0]['d:status'][0];
this.ccData.push({
key: 'userStatus',
value: status
});
resolve();
});
},
);
});
}

NodeJS NPM soap - how do I chain async methods without callbacks (ie use async or Promise)?

I have successfully called a sequence of soap webservice methods using nodejs/javascript, but using callbacks... right now it looks something like this:
soap.createClient(wsdlUrl, function (err, soapClient) {
console.log("soap.createClient();");
if (err) {
console.log("error", err);
}
soapClient.method1(soaprequest1, function (err, result, raw, headers) {
if (err) {
console.log("Security_Authenticate error", err);
}
soapClient.method2(soaprequest2, function (err, result, raw, headers) {
if (err) {
console.log("Air_MultiAvailability error", err);
}
//etc...
});
});
});
I'm trying to get to something cleaner using Promise or async, similar to this (based on the example in the docs here https://www.npmjs.com/package/soap) :
var soap = require('soap');
soap.createClientAsync(wsdlURL)
.then((client) => {
return client.method1(soaprequest1);
})
.then((response) => {
return client.method2(soaprequest2);
});//... etc
My issue is that in the latter example, the soap client is no longer accessible after the first call and it typically returns a 'not defined' error...
is there a 'clean' way of carrying an object through this kind of chaining to be used/accessible in subsequent calls ?
Use async/await syntax.
const soap = require('soap');
(async () => {
const client = await soap.createClientAsync(wsdlURL);
cosnt response = await client.method1Async(soaprequest1);
await method2(soaprequest2);
})();
Pay attention to Async on both createClient and method1
In order to keep the chain of promises flat, you can assign the instance of soap to a variable in the outer scope:
let client = null;
soap.createClientAsync(wsdlURL)
.then((instance) => {
client = instance
})
.then(() => {
return client.method1(soaprequest2);
})
.then((response) => {
return client.method2(soaprequest2);
});
Another option would be nested chain method calls after the client is resolved:
soap.createClientAsync(wsdlURL)
.then((client) => {
Promise.resolve()
.then(() => {
return client.method1(soaprequest2);
})
.then((response) => {
return client.method2(soaprequest2);
});
})

my graphql server mutation return null value

I am having challenges retrieving the results of my mutation. I need to create a db record and send an email notifying to user that the registration was successful. since both the sending of the email and the db update is server side I want to do both in the same mutation. If the email message fail the db must not be updated. So I have the following Mutation:
Mutation: {
createDealer(_, params) {
console.log("params: " + JSON.stringify(params));
bcrypt.genSalt(10, function(err, salt) {
bcrypt.hash(params.dealer.password, salt, function(err, hash) {
// Store hash in your password DB.
console.log("hashed password " + params.dealer.password)
params.dealer.password = hash;
console.log("hashed password " + params.dealer.password + " Hash: " + hash);
let session = driver.session();
let query = "CREATE (d:Dealer {email:$dealer.email}) SET d += $dealer RETURN d";
let here = "here".link("mymail#example.com");
let messageObj = {
to: params.dealer.email,
subject: 'Dealer Registration',
text: `Thank you for signing up. To complete and activate your registration please click ${here}.`
}
return (sendEmail(messageObj))
.then(data => {
console.log('SendMail data....' + JSON.stringify(data));
return session.run(query, params)
})
.then(result => {
console.log('SendNeo4j data....' + JSON.stringify(result));
return result.records[0].get("d").properties
})
.catch((err) => {
console.log(err);
});
//});
});
}); // genSalt
} // Create Dealer
}, // Mutation
Even thought both actions are successful I can't seem to retrieve the results. I get 'undefined' for:
console.log('SendMail data....' + JSON.stringify(data));
while
console.log('SendNeo4j data....' + JSON.stringify(result));
does display the correct data
but graphiql returns 'null' for the mutate.
this is the graphiql mutation:
mutation CreateDealer($dealer: DealerInput!) {
createDealer(dealer: $dealer) {
email
name
}
}
with the DealerInput variables of course.
I have read where you can retrieve multiple results from a query/mutation but I am not sure how it works. Here I need both the results of the sendEmail and the db update for my Angular/apollo front-end....I would imaging graphiql knows nothing of the sendEmail but I expected it to return the properties I requested.
SendEmail:
module.exports = (message) =>
new Promise((resolve, reject) => {
const data = {
from: 'mymail#example.com',
to: message.to,
subject: message.subject,
text: message.text
};
mailgun.messages().send(data, (error) => {
if (error) {
return reject(error);
}
return resolve();
});
});
Can someone with a little more experience than I help me out here...thanks
Couple of things to fix here. Returning a Promise (or any other value) inside a callback doesn't do anything, and doing so won't let you chain additional Promises like you want. Instead, your promise gets fired off inside the callback and isn't awaited.
As a general rule of thumb, don't mix Promises and callbacks. If you absolutely have to use callbacks, always wrap the callback in a Promise (like you did inside sendMail). Luckily, most popular libraries today support both callbacks and Promises. Here's how you could refactor the code above to correctly chain all your Promises:
createDealer(_, params) {
return bcrypt.hash(params.dealer.password, 10) // note the return here!
.then(hash => {
params.dealer.password = hash
const session = driver.session()
const query = "CREATE (d:Dealer {email:$dealer.email}) SET d += $dealer RETURN d"
const here = "here".link("mymail#example.com")
const messageObj = {
to: params.dealer.email,
subject: 'Dealer Registration',
text: `Thank you for signing up. To complete and activate your registration please click ${here}.`
}
return sendEmail(messageObj) // note the return here!
}).then(data => {
return session.run(query, params) // note the return here!
}).then(result => {
result.records[0].get("d").properties // note the return here!
})
bcrypt.hash will autogenerate the salt for you if you don't pass one in -- there's no need to call two separate functions
We kick off our Promise chain with bcrypt.hash, so we need to return the Promise it returns. A resolver must return a value or a Promise that will resolve to a value, otherwise it returns null.
Inside each then, we return a Promise. This way we "chain" our Promises, allowing the final value we return in the resolver to be the value the very last Promise in the chain resolves to.
We need to also fix your sendMail function to actually return the value. You're correctly returning the new Promise inside the function, but you also need to pass the returned data object to resolve. That tells the Promise to resolve to that value.
module.exports = (message) => new Promise((resolve, reject) => {
const data = // ...etc
mailgun.messages().send(data, (error) => {
if (error) reject(error) // no need to return, it's pointless
resolve(data) // pass data to resolve
})
})
Side note: looks like the official mailgun library supports Promises.
Additionally, I would strongly encourage you to look into using async/await, especially when dealing with a long Promise chain. It's less error prone and more readable:
createDealer async (_, params) {
const hash = await bcrypt.hash(params.dealer.password)
params.dealer.password = hash
const session = driver.session()
const query = "CREATE (d:Dealer {email:$dealer.email}) SET d += $dealer RETURN d"
const here = "here".link("mymail#example.com")
const messageObj = {
to: params.dealer.email,
subject: 'Dealer Registration',
text: `Thank you for signing up. To complete and activate your registration please click ${here}.`
}
const emailResult = await sendEmail(messageObj)
const result = await session.run(query, params)
return result.records[0].get("d").properties // still need to return!
}
EDIT: With regard to catching errors, GraphQL will catch any errors thrown by your resolver, which means you can often skip using catch yourself. For example, if your mailgun request fails, it'll generate some kind of error and your query will return null for data and the error details inside of the errors array.
That may be sufficient, although 1) you may want to log your error's stack elsewhere; and 2) in production, you probably don't want to expose internal error details to the public.
That means you'll probably want to use custom errors. As a bonus, you can add some custom properties to your errors to help the client deal with them eloquently. So your code may end up looking more like this:
class DeliveryFailureError extends Error {}
DeliveryFailureError.code = 'DELIVERY_FAILURE'
DeliveryFailureError.message = 'Sorry, we could not deliver the email to your account'
try {
await mailgun.messages.create()
} catch (err) {
logger.error('Mailgun request failed:', err.stack)
throw new DeliveryFailureError()
}

How ensure async request has finished before running a function

I am performing an async request to pull data from a server and then call a function after the request. My question is how do I ensure the request is complete and all data loaded before processRecords() runs?
Thanks in advance.
function getRecords () {
var ids = Server.getIds();
var allTheRecords = [];
ids.forEach(function(recordId) {
Server.getRecord(recordId, function (error, data) {
if(error) {
console.log(error);
} else {
allTheRecords.push(data);
};
});
});
processRecords(allTheRecords);
}
How are you performing the Asynchronous request? If it's an AJAX request, the API provides for callbacks to be supplied based on the result of the call.
https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest
You could use the native Promise api to perform the async actions for you.
Using Promise.all you can give it an array of promises that need to be resolved before calling the processRecords function.
It also now more reusable as you have a getRecord function that you could use elsewhere in your code.
You should probably think of a way to add in the ability to get multiple records from the server if you control it though. You don't really want to fire off a bunch of network requests if you can do it in just one.
// Server mock of the api you have shown
const Server = {
getRecord(id, callback) {
console.log('getRecord', id)
callback(null, {id})
},
getIds() {
return [1, 2, 3]
}
}
function getRecords (ids, processRecords) {
console.log('getRecords', ids.join())
// mapping the array of id's will convert them to an
// array of Promises by calling getRecord with the id
Promise.all(ids.map(getRecord))
// then is called once all the promises are resolved
.then(processRecords)
// this will be called if the reject function of any
// promise is called
.catch(console.error.bind(console))
}
function getRecord(recordId) {
// this function returns a Promise that wraps your
// server call
return new Promise((resolve, reject) => {
Server.getRecord(recordId, function (error, data) {
if(error) {
reject(error)
} else {
resolve(data)
}
})
})
}
getRecords(Server.getIds(), function(records) {
console.log('resolved all promises')
console.log(records)
})

Categories