When I write new code in Node, I use Koa and generators, as it makes the code simpler, and faster to develop. I do not have good support for generators in in-browser JS AFAIK, and have not seen them outside of Node libraries.
There are millions of articles on line to help people from the job title “shelf stacker”/ “sales director” (or similar, random non-tech titles) to the title “evening code-fiddler”. The articles are trying to make development look as simple as possible; they skip all the error handling, they skip performance analsys, they skip encapsulation and reuse. They have been written last some point in the last decade (i.e. not necessarily current). They all nest callbacks in an unmanagable fashion.
For example X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X

The following is my preferred solution, I am getting sick of the above articles messes. Secondly I wish to avoid global variables, which many JS examples litter all over the place. There are many XMLHTTPRequest wrappers, and this isn't the point of this article. It is assumed that there is a library that exports Promises, and does all the REST verbs. I generally try to write code that can run in a browser directly, as it makes wide browser support faster/cheaper. This following code is optimised for a chain of API calls, where data needs to flow from one to the next. I am writing this down, purely for reference, and that I am practicing what I preach.

// The basic unit in this is a set of functions put into a object. These should 
// be viewed as structs, not classes, as it is poor OO.
// The point of doing this is to associate a response to a request, but not nest 
// them.
// Outline reference 
let api1= {
	reqt:function(env) {
		// ...
	},
	resp:function(response) {
		// ...
	},
	error:function(response) {
		// ....
	}
};
// Please note the error handler is always present.
// As a sequence, running the reqt function should start a AJAJ call, chaining 
// the resp and error Promises/callbacks to the relevant places in the networking 
// library.

// I create a AJAJ call stack using the following API:
// Function names chosen to match Array class API on purpose.
const AJAJStack={
	create:function() {
		this.stack=[];
		return this;
	},
	// for normal stacking, this is easier to read
	push:function(api) {
		this.stack.push(api);
	},
	// for in-flight edits, this is more useful
	unshift:function(api) {
		this.stack.unshift(api);
	},
	shift:function() {
		return this.stack.shift();
	},
	reduce:function(env, tick, delay) {	
		let _self=this;
		if(delay) {
			let cur=this.stack.shift();
			cur.reqt(env);
		}
		for(let i=0; i<this.stack.length; i++) {
			window.setTimeout(function() { 
				let cur=_self.stack.shift(); 
				cur.reqt(env);
			}, tick*(i+1));
		}
	}
};

// I assert I have a networking library, e.g. Axios, Vue-resource, Angular.$http,
// jQuery.ajax etc. The variable is called ajaj in the following, as I haven't 
// used XML in years.
// I reference env several times. The above classes are to be used in a third 
// service class. The service class has any necessary consts (see URLS), a 
// reference to ajaj object, and a reference to the stack object. If you need 
// to do things like "try X() up to five times, before failing", the counter for 
// previousFailures is stored in the Service.  Env is a reference to the Service. 
// I use DI, it makes testing much faster.
// Example:
const SearchAPIService ={
	create:function(stack, ajaj, tick) {
		this.stack=stack;
		this.ajaj=ajaj;
		this.tick=tick;
		return this;
	},
	search:function(next, param) {
		let myReferenceId=null;
		let previousFailures=0;

		let api1= {
			reqt:function(env) {
				// ...
				// use param
				// triggers resp and error
			},
			resp:function(response) {
				// ...
				// writes to myReferenceId
			},
			error:function(response) {
				// ....
				// deletes next step....
			}
		};
		let api2= {
			reqt:function(env) {
				// ...
				// uses myReferenceId and use param
				// triggers resp and error
			},
			resp:function(response) {
				// ...
				// if get results.count === 0, re-queues self with stack.unshift
				// unless previousFailures >5, in which case delete stack items
				// if have results, call next(results)
			},
			error:function(response) {
				// ....
			}
		};

		this.stack.push(api1);
		this.stack.push(api2);
		this.stack.reduce(this, this.tick);
	} 

}
// all the variables are constrained to the search function, making it fairly 
// stateless

This is fairly real error handling, and the code is readable, and async. All of the domain logic is isolated into the structs, and as this uses DI, it can be easily tested. Networking and timing code is a hard combination to test reliably. The structs could be held outside of the function, but I think its clearer with respect the local variables doing it as such.
If your usecase is literally a(), b(), c() API calls; then set the tick to be really large; and trigger the next struct from inside the previous one.
If your usecase is a(), allow user action, b(), allow, c(); then setting the tick to a few seconds, and don't trigger anything; is more sensible.