Consider the following non-negotiables:
There are 3 async functions that I have to work with and they're below, I cannot change these.
async asyncCall1({ caseId }: { caseId: string }) {
const response = await this.apolloClient.query({...})
return {
...response,
data: caseResponse,
};
}
async asyncCall2({ caseIdFromCall1 }: { caseIdFromCall1: string }) {
const response = await this.apolloClient.query({...})
return {
...response,
data: caseResponse,
};
}
async asyncCall3({ caseIdFromCall3 }: { caseIdFromCall3: string }) {
const response = await this.apolloClient.query({...})
return {
...response,
data: caseResponse,
};
}
What I need happen is:
Need one field from response of asyncCall1 to feed into asyncCall2, I then need one field from asyncCall2 to feed as input to asyncCall 3. Essentially the calls have to happen sequentially.
I need to do add to a list from asyncCall1 and asyncCall2 and asyncCall3 and return a boolean based on a condition while processing asyncCall3.
A way to handle exceptions in each? - Perhaps return a certain boolean value in each case we run into an error in any of the calls.
I'm happy if a solution gives me a way to move away from nested hell
Here is my try
const checkAuthorization = (input: string) => {
let coalate = []
dependency1.asyncCall1({ caseId: input })
.then((call1RawResult) => {
console.log("Inside caseResult: ", call1Result);
return { data: call1Result.data.caseProperties.caseId1 }; //This is the response from call 1 that I want to pass to call 2
})
.then((call1SpecificField) => dependency2.asyncCall2({caseId: call1SpecificField.data})) //This is where I attempt to use value from asyncCall1
.then((call2RawResult) => {
const caseId2 = call2RawResult.data.caseProperties.caseId2;
coalate.push(caseId2); //Also push to list
return { data: caseId2 } //Do some operation and pass this on for use in the next call
})
.then((call2SpecificField) => dependency3.asyncCall3({caseId: call2SpecificField.data}))
.then((call3RawResult) => {
const caseId3 = call3RawResult.data.caseProperties.caseId2;
coalate.push(caseId3); //Also push to list
//Finally return true or false if input is found in consolidated coalate list
return coalate.includes(input);
}).catch((error) => { //If any issue in any of the above dependency calls - return false.
console.log(error)
return false;
})
}
Inside caseResult:
, but never beyond that.checkAuthorization()
, I need the computed boolean value from inside the catch block atleast, but it just comes out as undefined.await
instead of .then()
callback-hell results in code that's much easier to read, write, and understand, in my opinion.
await
does not mean you have to await
each Promise
when they enter the scope: it's perfectly valid to store a returned/received Promise
in a local and then await
that local later-on - this is how you can implement certain kinds of nontrivial Promise logic.I'll tackle each of your goals individually and incrementally:
- Need one field from response of
asyncCall1
to feed intoasyncCall2
.- I then need one field from
asyncCall2
to feed as input toasyncCall3
.- Essentially the calls have to happen sequentially.
That's straightforward:
(btw, svc: MyService
is the object
with those asyncCallN
methods).
async function run( svc: MyService, input: string ) {
const result1 = await svc.asyncCall1( { caseId: input } );
console.log( "result1: %o", result1 );
const caseId1 = result1.data.caseProperties.caseId1;
const result2 = await svc.asyncCall2( { caseId: caseId1 } );
console.log( "result2: %o", result2 );
const caseId2 = result2.data.caseProperties.caseId2;
const result3 = await svc.asyncCall3( { caseId: caseId2 } );
console.log( "result3: %o", result3 );
const caseId3 = result3.data.caseProperties.caseId2; // <-- Not `caseId3` ?
}
I need to do add to a list from
asyncCall1
andasyncCall2
andasyncCall3
and return aboolean
based on a condition while processingasyncCall3
.
Again, straightforward:
(though I feel a Set<string>
should be used instead of an Array<string>
/ String[]
because your code will be performing set-membership checks, which are O(1)
(good) for Set<T>
but O(n)
(bad) for Array<T>
).
async function run( svc: MyService, input: string ): Promise<boolean> {
const caseIdsSet = new Set<string>();
const result1 = await svc.asyncCall1( { caseId: input } );
console.log( "result1: %o", result1 );
const caseId1 = result1.data.caseProperties.caseId1;
caseIdsSet.add( caseId1 );
const result2 = await svc.asyncCall2( { caseId: caseId1 } );
console.log( "result2: %o", result2 );
const caseId2 = result2.data.caseProperties.caseId2;
caseIdsSet.add( caseId2 );
const result3 = await svc.asyncCall3( { caseId: caseId2 } );
console.log( "result3: %o", result3 );
const caseId3 = result3.data.caseProperties.caseId2; // <-- Not `caseId3` ?
caseIdsSet.add( caseId3 );
return caseIdsSet.has( input );
}
A way to handle exceptions in each? - Perhaps return a certain boolean value in each case we run into an error in any of the calls.
Only catch exceptions if you actually need to: Unless you expect any of those asyncCallN
calls to fail during normal program operation and your program needs to do something during an error condition (such as rolling-back any persisted changes so-far) then you don't need to litter your program code with low-value catch
statements.
Even if you want to just want to log any errors (e.g. catch( err ) { console.error(err); }
) you still shouldn't normally need any catch
blocks: most JS environments will/can still log exceptions at the point at which they are thrown by a throw
statement. If this is a JS that runs in a web-browser then you should also be listening to window
's 'error'
event for unhandled JS exceptions.
Your asyncCallN
functions seem to be using Apollo - if so, then you should (largely) ignore the vague, general advice that I wrote above and instead follow Apollo's guide to error-handling with their library.
I'm happy if a solution gives me a way to move away from nested hell
This answer.