Trying to interact with a JS API, but it fails when run by a Grunt task; I think my logic is confused. My steps:
check_tokens
)refresh_tokens
)authorize_with_api
) <- this is the issueauthorize_with_api
reject with error or resolve with tokensCurrently the Grunt task reports an UnhandledPromiseRejectionWarning
and never completes. If I comment out the call to authorize_with_api
it then exits properly with an error, and I get my topmost caught error!
message printed.
Why can't I return a promise out of an executor function? What's wrong with my logic?
/* global sdk, config, tokens */
return getTokens().then((p_tokens) => {
tokens = p_tokens;
return check_tokens(tokens);
}).then((tokens) => {
console.log('then() is called!');
}).catch((err) => {
console.error('caught error!', err);
});
function check_tokens(tokens) {
if(are_old(tokens)) { // returns true
return refresh_tokens(tokens);
}
return Promise.resolve(tokens);
}
function refresh_tokens(tokens) {
return new Promise(function(resolve, reject) {
sdk.refreshTokens(tokens.refresh_token, function(err, new_tokens) {
if(err) {
if(error.code === 'invalid_grant') {
return authorize_with_api();
}
reject('refreshTokens failed');
} else if(newTokens) {
resolve(new_tokens);
}
});
});
}
function authorize_with_api() {
return new Promise(function(resolve, reject) {
sdk.getTokens(config.auth_code, function(err, tokens) {
if(err) {
reject('getTokens failed');
} else if(tokens) {
resolve(tokens);
}
});
});
}
Returning from a Promise constructor (or any function within it) does not resolve a promise:
return new Promise(function(resolve, reject) {
sdk.refreshTokens(..., function(err, new_tokens) {
if(error.code === 'invalid_grant') {
return authorize_with_api();
} // ^--- this will not chain to the promise being created.
Even if you didn't have the return from the sdk.refreshTokens
callback and instead had a direct return authorize_with_api()
without the callback, the result would still not get chained.
To resolve a promise, you cannot return from its constructor but must explicitly call one of the given callbacks (resolve/reject) instead:
return new Promise(function(resolve, reject) {
sdk.refreshTokens(..., function(err, new_tokens) {
if(error.code === 'invalid_grant') {
resolve(authorize_with_api());
} // ^--- must call resolve here
Resolving a promise actually handles rejection as well so no matter if authorize_with_api
resolves or rejects, the state will propagate up the chain accordingly.
My suggestion is to still keep the return
statement to maintain the intended visual semantics of the if
branch conditioning an early return but the code will work without it because Promises can only be resolved once and all further calls to reject
/resolve
are ignored.
return new Promise(function(resolve, reject) {
sdk.refreshTokens(..., function(err, new_tokens) {
if(error.code === 'invalid_grant') {
return resolve(authorize_with_api());
} // ^--- should still return here for readability - clean logic purposes
reject('refreshTokens failed'); // this will be ignored if the above `resolve` gets called first, no matter if you have the `return` statement
Examples:
function success() {
return Promise.resolve('success');
}
function error() {
return Promise.reject('error');
}
function alwaysPending() {
return new Promise(() => {
return success();
});
}
function resolves() {
return new Promise((resolve) => {
resolve(success());
});
}
function rejects() {
return new Promise((resolve) => {
resolve(error());
});
}
alwaysPending().then(console.log); // doesn't log anything
resolves().then(console.log);
rejects().catch(console.log);
Just write else statement of last if:
function authorize_with_api() {
return new Promise(function(resolve, reject) {
sdk.getTokens(config.auth_code, function(err, tokens) {
if(err) {
reject('getTokens failed');
} else if(tokens) {
resolve(tokens);
} else {
reject('tokens == undefined && err == undefined');
}
});
});
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With