I'm trying to force Node to wait for either a success or a failure. I understood fetch to return a promise and I thought I told it how to handle both.
The following code does not honor the await I asked it to do:
async function getAccessToken() {
...
let fetchResult = await fetch(argumentParserResult.authorizationUrl, {
method: 'POST',
body: formData,
headers: headers
}).then(success => {
console.log("Success reached. " JSON.stringify(success));
process.exit(2);
}, other => {
console.log("Other reached. " JSON.stringify(other));
process.exit(3);
});
console.log('@@ after fetch fetchResult=' fetchResult);
...
}
You might think that the await would cause it to, wait for the Promise to complete, but instead it leaves the whole function, and goes back to the caller. It does not print the '@@ after fetch fetchResult=' line. Neither the failure, nor success handler is executed.
I should point out that it also does not appear to make the requested POST call either. Instead, it sees that request and does something completely different without raising any exception.
Why is it not honoring the 'await' keyword whatsoever?
--- If I try the try/catch approach as follows:
async function getAccessToken() {
console.log('@@getAccessToken BP1');
if (argumentParserResult.authenticationScheme == 'OAUTH2') {
console.log('@@getAccessToken BP2');
const fetch = require('node-fetch');
const url = argumentParserResult.resourceUrl;
console.log('@@getAccessToken BP3');
let formData = new URLSearchParams({
'grant_type': 'client_credentials',
'client_id': argumentParserResult.clientId,
'scope': argumentParserResult.clientScope,
'client_secret': argumentParserResult.clientSecret
})
console.log('@@getAccessToken BP4');
let headers = {
'Content-Type': 'application/x-www-form-urlencoded'
};
console.log('@@getAccessToken BP5');
console.log('POST ' argumentParserResult.authorizationUrl);
console.log(JSON.stringify(formData));
console.log('@@getAccessToken BP6');
try {
console.log('@@getAccessToken BP7');
const response = await fetch(argumentParserResult.authorizationUrl, {
method: 'POST',
body: formData,
headers,
});
console.log('@@getAccessToken BP8');
console.log(`Success reached.`, JSON.stringify(response));
const json = await response.json();
console.log('@@getAccessToken BP9');
console.log(`Other reached.`, json);
return json;
} catch (error) {
console.log('@@getAccessToken BP10');
console.log(`!! something went wrong`, error.message);
console.error(error);
return error;
} finally {
console.log('@@getAccessToken BP11');
console.log(`fetch finished`);
}
console.log('@@getAccessToken BP12');
}
console.log('@@getAccessToken BP13');
return "Should not have reached this point";
}
I get
@@getAccessToken BP1
@@getAccessToken BP2
@@getAccessToken BP3
@@getAccessToken BP4
@@getAccessToken BP5
POST https://some-url
{}
@@getAccessToken BP6
@@getAccessToken BP7
As you can see, it goes just inside of the try block, then goes back to the caller without triggering the finally, error handlers or the logging after the fetch.
Using the .then approach as follows:
async function getAccessToken() {
console.log('@@getAccessToken BP1');
if (argumentParserResult.authenticationScheme == 'OAUTH2') {
console.log('@@getAccessToken BP2');
const fetch = require('node-fetch');
const url = argumentParserResult.resourceUrl;
console.log('@@BP1.9');
let formData = new URLSearchParams({
'grant_type': 'client_credentials',
'client_id': argumentParserResult.clientId,
'scope': argumentParserResult.clientScope,
'client_secret': argumentParserResult.clientSecret
})
console.log('@@getAccessToken BP3');
let headers = {
'Content-Type': 'application/x-www-form-urlencoded'
};
console.log('@@getAccessToken BP4');
console.log('POST ' argumentParserResult.authorizationUrl);
console.log(JSON.stringify(formData));
let response = await fetch(argumentParserResult.authorizationUrl, {
method: 'POST',
body: formData,
headers: headers
}).then(success => {
console.log('@@getAccessToken BP5');
console.log("Success reached. " JSON.stringify(success));
return success // !--> LOOK HERE, you should return the success variable
}).catch(e => {
console.log('@@getAccessToken BP6');
console.log(e) // !--> LOOK HERE, if you catch the error, no error will be thrown to the caller
return e
});
console.log('@@getAccessToken BP7');
console.log('@@ after fetch fetchResult=', fetchResult); // !--> LOOK HERE, this log will always log something now, it could be the responso or the error
}
console.log('@@getAccessToken BP8');
}
I get these logs:
@@getAccessToken BP1
@@getAccessToken BP2
@@BP1.9
@@getAccessToken BP3
@@getAccessToken BP4
POST https://login.microsoftonline.com/5a9bb941-ba53-48d3-b086-2927fea7bf01/oauth2/v2.0/token
{}
As you can see above, it goes just to the point of the fetch, then returns to the calling function.
In neither case, can I see any evidence that the fetch was ever called.
CodePudding user response:
Try this:
async function getAccessToken() {
try {
const response = await fetch(argumentParserResult.authorizationUrl, {
method: 'POST',
body: formData,
headers,
});
console.log(`Success reached.`, JSON.stringify(response));
const json = await response.json();
console.log(`Other reached.`, json);
} catch (error) {
console.log(`!! something went wrong`, error.message);
console.error(error);
} finally {
console.log(`fetch finished`);
}
}
You don't need to use thenable object when writing with async/await, instead, catch the error with a try catch bloc, and just get the async value using return of await
ed function.
CodePudding user response:
You are mixing await
and then
. It is not forbidden, but in most simple case you don't need it.
Solution without then
:
async function getAccessToken() {
try {
console.log('fetching data') // this log will always appear as first log, before fetching data
let fetchResult = await fetch(argumentParserResult.authorizationUrl,
{
method: 'POST',
body: formData,
headers: headers
})
let jsonR = await fetchResult.json()
console.log('fetch done') // this log will appear only if fetch is done with no errors
} catch (e) {
console.error('something went wrong', e) // this log will appear only if there was an error
}
console.log('after all') // this log will appear always, after fetch (even if fetch fails or not)
}
Solution with then
:
async function getAccessToken() {
let fetchResult = await fetch(argumentParserResult.authorizationUrl, {
method: 'POST',
body: formData,
headers: headers
}).then(success => {
console.log("Success reached. " JSON.stringify(success));
return success // !--> LOOK HERE, you should return the success variable
}).catch(e => {
console.log(e) // !--> LOOK HERE, if you catch the error, no error will be thrown to the caller
return e
});
console.log('@@ after fetch fetchResult=', fetchResult); // !--> LOOK HERE, this log will always log something now, it could be the responso or the error
}
As you can see, error handling is not quite convenient in the second solution. That's why you should not mix await
with then
, unless you know what you are doing
CodePudding user response:
The point of async
/await
is to get rid of the callbacks and make the code more procedural. Your code:
async function getAccessToken() {
...
let fetchResult = await fetch(argumentParserResult.authorizationUrl, {
method: 'POST',
body: formData,
headers: headers
})
.then( success => {
console.log("Success reached. " JSON.stringify(success));
process.exit(2);
}, other => {
console.log("Other reached. " JSON.stringify(other));
process.exit(3);
});
console.log('@@ after fetch fetchResult=' fetchResult);
...
}
fails, because you are
- Waiting for
fetch()
to resolve and return a result, and - In your
then()
chain, you are - Invoking
process.exit()
in the case of either success or failure.
Than means you kill the entire process as soon as the call to fetch()
resolves with either a success or a failure.
If you do something like this:
async function getAccessToken() {
...
const opts = {
method: 'POST',
body: formData,
headers: headers
};
const {json, err} = await execFetch( argumentParserResult.authorizationUrl, opts );
if ( err ) {
console.log("that didn't work!", err);
process.exit(1);
}
...
}
async function execFetch( url, opts ) {
const response = { json: undefined, err: undefined };
const { res, err } = await fetch( argumentParserResult.authorizationUrl, opts )
.then( res => ({ res , err: undefined }) )
.catch( err => ({ res: undefined , err }) );
if ( err ) {
response.err = err;
}
else if ( !res.ok ) {
// non-2xx HTTP status
response.err = new Error(`${res.status}: ${res.statusText}`);
}
else {
// the 2xx happy path: deserialize the JSON response body into a JS object
response.json = res.json();
}
return response;
}
Your call to fetch()
will always succeed and hand you back a tuple with a json
and an err
property.
A successful call will return something like this:
{
json: { a: 1, b: 2, c: 3, },
err: undefined,
}
Whilst a call to fetch()
that fails will return something like this:
{
json: undefined ,
err: /* some error object with details about what went south */,
}