What I'm trying to do is: from my single page application, get data from an API using fetch. If the JWT is expired, ask the API for a new JWT and then renew the request for data.
I tried to implement this with 3 functions: getData
calls fetchData
. If fetchData
returns a JWT expired
message, a function getjwt
is called (also from within getdata
) and then (only then) fetchData
again.
The application calls getData
(upon page load - but that's irrelevant here)
I have a function getjwt
:
async function getjwt() {
// posts a refresh token and gets a jwt in headers
let adresse = `${baseUrl}/auth/getjwt`
let headers = { 'Content-Type': 'application/json' }
postData(adresse, refresh.value, headers)
.then((response) => {
if (response.status === 200) {
jwt.value = response.headers.get('__auth__')
}
})
.catch((err) => alert(err))
}
and the functions fetchData
and getData
:
async function fetchData (url = '', headers = {}) {
let r = await fetch(url, {
method: 'GET',
mode: 'cors',
cache: 'no-cache',
credentials: 'include',
headers: headers,
redirect: 'follow',
referrerPolicy: 'no-referrer',
})
return r
}
async function getData(url = '', headers = {}) {
let r = await fetchData(url, headers)
if (r.status === 409) {
console.log("Oh no! My token has expired.")
getjwt()
let r = await fetchData(url, headers)
return r.json()
}
return r.json()
}
On purpose, first time fetchData is called, the jwt is expired. getjwt
is called and the jwt get properly renewed (in a Pinia store). BUT: the order of execution is: getData
gets called twice, and then getjwt
is called.
Maybe I'm missing something about asynchroneous operations and Promise, but how can I force getjwt
to be called and finish getting the new jwt before getData
is called a second time.
Problem with the getJwt
function is that the promise returned by it doesn't wait for the asynchronous operation inside it to finish. As a result, the promise it returns gets resolved before postData
gets completed.
You can fix this by returning the promise from postData
as shown below:
async function getjwt() {
...
return postData(...)
.then((response) => {
...
})
.catch((err) => {
...
throw err;
});
}
Adding a return
before the postData
call fixes the issue because return
ensures that the promise returned by the getJwt
function gets resolved to the promise returned by return postData(...)
. One promise p1
getting resolved to another promise p2
simply means that the promise p1
will wait for p2
to settle (fulfil or reject). If p2
fulfils, p1
fulfils with the same value. If p2
gets rejected, p1
also gets rejected with the same rejection reason.
Note the change in the catch
method. The throw err
is needed to avoid implicitly converting the promise rejection into promise fulfilment.
As you aren't using the await
keyword inside the function, you might as well just remove the async
keyword from the function signature.
And finally, to make it work, you need to await
the call to the getJwt
function inside the getData
function.
async function getData(url = '', headers = {}) {
...
if (r.status === 409) {
...
await getjwt()
...
}
...
}
I would refactor your code by making the following two improvements:
catch
method call and handle the error in the function that calls the getJwt
function.async-await
async function getjwt() {
// posts a refresh token and gets a jwt in headers
let adresse = `${baseUrl}/auth/getjwt`;
let headers = { 'Content-Type': 'application/json' };
const response = await postData(adresse, refresh.value, headers);
if (response.status === 200) {
jwt.value = response.headers.get('__auth__');
}
}