Async requests in sequential code

I’m looking for advice for how to proceed with a serverless app I’m writing.

To start, I’ll say I’m totally new to node.js programming. The biggest challenge for me is trying to write a sequential set of actions using async requests module.

I’m implementing a serverless app that will update our freshdesk account. When it gets information sent, it will perform a number of actions hat involve REST API calls that must occur in sequence as follows:

  1. Check if account (by name) already exists for contact. Get account ID
  2. Add contact (using account id from above)
  3. Get Freshdesk ID from call above and post that to external system

I thought i’d be clever and write a function that I re-use for making all REST calls:

function myRequest(method, url, headers, body={}) {
  res=$request[method](url, { body: JSON.stringify(body), headers: headers } )
  res.then( requestSuccess, requestFail )
  function requestFail(err) { console.log(err) }
  function requestSuccess(data) { console.log(data); return data }
}

Note: I intentially re-write as callbacks because IMO the nested success/fail structures become a spaghetti mess.

Then call as in:

 url=fd_base_url + "/companies/autocomplete?name=" + company_name
  res=myRequest("get", url, fd_headers, body={})
  console.log("RESULT: ", res)

But I then realize that myRequest() returns before requestSuccess is able to create ‘data’. Thus, the return value of myRequest() is undefined.

The problem by nature is sequential. Seems poorly suited to a callback model. I think sync API calls would be far preferable.

How does one go about handling this sort problem?

Hey hi Steve,

You can use promise, i guess using promise would solve your problem. Create a function that would use promise and make use of request API or axios (anything you prefer).

For example,

let functionName = () => {
return new Promise((resolve, reject) => {
api call.then(data => {
resolve(data);
}).catch(reject)
})
}

This is my way of request API ,

let requestApi = function (options) {
return new Promise((resolve, reject) => {
client.request.get(options.url, options.opt).then(function (data) {
resolve(data.response);
}).catch(reject);
});
}
let request = {
url:"https://#domain/api/v2/#endpoint"
opt : { headers : {
"Authorization": "Basic <%= encode(iparam.FDapiKey) %>",
"Content-Type": "application/json; charset=utf-8"
}
}
}
requestApi(request).then(data =>{
// another functional call
function2(params )

})

let function2 = (param)=>{
requestAPI ({
url:url,
opt:header
})
}

Hope this helps.

2 Likes

I still don’t know enough to know but somehow that looks like chaining to me and that for a 3rd item in the sequence I’d need yet another level of nesting.

I found a solution which I like better which I’ll describe in separate reply.

p.s. @Janani thanks for your help. Your solution was quite valid but it seems i can only mark one item as the solution and I’m going to mark my solution as such since it gets closer to the stated goal of handling multiple Aync calls w/o too much nested code.

1 Like

After a bit more research I choose to use async/await because this allowed me to write a sequence of functions that look very much like a regular synchronous calls.

Promises are fine in theory until you try to use them in real cases. Everyone tells you how nice and simple promises look but they don’t only show simple examples. Once you add the error checking and data manipulation that has to go in between the API calls, you end up with completely unreadable code.

With aysnc/away, you can write one general use function:

const asyncRequest = (method, url, headers, body={}) => {
  return $request[method](url, { body: JSON.stringify(body), headers: headers } )
}

Then I can call this multiple times as in following (w/o the error checking):

createOrUpdateFDContact: async function (payload) {
  url=fd_base_url + "/companies/autocomplete?name=" + payload.data.map_fields.company_name
  res = await asyncRequest("get", url, fd_headers, body={})
  // Get comany ID from above and append to JSON
  url=fd_base_url + "/contacts"
  res = await asyncRequest("post", url, fd_headers, body=record)
  // Check if record added.  If not and "duplicate" record error, then get contact id and call following:
  res = await asyncRequest("put", url, fd_headers, body=record)
}

So as long as I’m inside createOrUpdateFDContact2, I can execute any number of API calls synchronously.

Below is more like a real world example of this. I am confident it would be unreadable/unmaintainable if implemented with the nested Promise reject/resolve/catch structure.

createOrUpdateFDContact2: async function (payload) {
	
	initialize_globals(payload) // Gets API key and Domain from iparams
	record=payload.data.record // JSON already in FD Contact structure

	// Check to see if the company exists already
	try {
		url=fd_base_url + "/companies/autocomplete?name=" + payload.data.map_fields.company_name
		res = await asyncRequest("get", url, fd_headers, body={})
	}
	catch(err) {
		console.log("Error getting companies: ", err)
	}
	
	// Parse list of companies to find if we link company or create new
	try {
		// Add company to either company or source_company based on result above
		companies=JSON.parse(res.response).companies
		if(companies.length==0) {
			// No matching company. Add company name to custom_fields
			record.custom_fields.source_company=payload.data.map_fields.company_name
		} else { // loop thru all to find exact match
			for(let cname of companies) {
				if(cname==payload.data.map_fields.company_name) record.companies_id=companies.id
			}
		}
		// TODO: What if none match?  
	}
	catch(err) {
		console.log(err)
	}

  // Try to create contact.
  // If fail then user most likely already exists and we need to update instead
  try {
	  url=fd_base_url + "/contacts"
		res = await asyncRequest("post", url, fd_headers, body=record)
  }
  catch(err) {  // err is actually API response object containing list of errors
  	// Loop thru errors and look for code "duplicate_value". If so, call update on additional_info.user_id
		fd_errors=JSON.parse(err.response).errors
  	dup=false
		for(let fd_err of fd_errors) {
			if(fd_err.code=="duplicate_value") dup=true
		}

		// If Add Contact resulted in duplicate_value, then try to update contact
		if(dup) {
			try {
				// Update contact with additional_info.user_id
				url=fd_base_url + "/contacts/" + errors[0].additional_info.user_id
				res = await asyncRequest("put", url, fd_headers, body=record)
				if(res.status!=200) {
					// If updating record fails, then show error from add API call as well as output from this call
					console.log("Add Contact API Error: ", err)
					console.log("Update Contact API Result: ", res)
				}
			}
			catch(err) {
				console.log(err)
			}
		}
  }
},

its obvious to many but for newbies like me, I’ll just explain that the key to making the API calls syncronous is the “await” keyword in front of the call to asyncRequest. Otherwise you get a promise back and not a result.

Some notes:

createOrUpdateFDContactis invoked from the events callback handler list:

  events: [
    { event: 'onAppInstall', callback: 'onAppInstallHandler' },
    { event: 'onExternalEvent', callback: 'createOrUpdateFDContact' },
    ...
  ],

Also, if you are trying to understand stuff, put a console.log(res) to show the result of each API call. It will make things a lot more clear.

2 Likes