[英]how do I defer javascript execution until an asynchronous method completes execution
[英]How do I defer the execution of code until fetch returns value
我創建了以下兩個函數來從后端獲取JWT Token
並將其存儲在localStorage
。 邏輯看似很簡單,但無法正常工作,因為在解析令牌的值之前,代碼將繼續執行。 結果,鏈中需要令牌的功能無法獲得令牌。 最終,它確實獲得了令牌並將其存儲在localStorage
但是到那時,我在啟動鏈中的所有函數都無法獲得有效的令牌。
在我的React應用程序啟動時,我從一開始就做所有這一切。 因此,想法是獲取令牌,然后執行其余的啟動步驟。
因此,我在應用程序的入口點調用myStartup()
函數。 我這樣做是第一步。 從那里,其余的啟動函數作為回調傳遞。
export const getJwtTokenFromApi = () => {
var request = new Request('/api/token', {
method: 'GET',
mode: 'cors',
credentials: 'include'
});
fetch(request)
.then((response) => {
response.text()
.then((token) => {
if(token.length > 0) {
localStorage.setItem('myToken', token);
return token;
} else {
return null;
}
})
})
.catch(err => {
});
}
export const getJwtToken = () => {
let token = localStorage.getItem('myToken');
if (token == null)
token = getJwtTokenFromApi();
return token;
}
export const myStartup = (callback) => {
const token = getJwtToken();
callback(token);
}
最終發生的事情是,需要令牌的調用函數直到收到令牌后才被推遲,因此最終接收到undefined
。
如何確保我將需要令牌的函數的執行推遲到擁有合法令牌之前-或至少為null值,這意味着我的API調用失敗?
您的函數getJwtToken
應該返回promise:
export const getJwtToken = () => {
let token = localStorage.getItem('myToken');
return token ? Promise.resolve(token) : getJwtTokenFromApi(storeToken)
}
在您的呼叫者中,令牌將被包裝在返回的內部Promise中:
getJwtToken().then(token => doSomething(token))
根本的問題是您沒有像異步一樣處理所有異步內容。 這是一個相當復雜的工作流程,混雜了阻塞和非阻塞任務,這意味着我們需要始終應用異步模式。 讓我們一次一步地完成腳本的一個功能。
這似乎是腳本的入口點:
export const myStartup = (callback) => {
const token = getJwtToken();
callback(token);
}
由於getJwtToken
是異步的,因此它將不起作用,這意味着其值將不可用於下一行的callback
。
我們如何知道getJwtToken
是異步的? 因為getJwtToken
調用getJwtTokenFromApi
,后者調用fetch
(規范告訴我們異步)。 由於getJwtToken
包裝了異步行為,因此它本身就是異步的。
由於getJwtToken
是異步的,因此我們知道,當callback
需要它時,第二行將無法使用token
。 實際上, token
在該范圍內將永遠不可用,因為getJwtToken
返回一個Promise,其解析值僅在.then
處理程序中可用。 因此,第一步是重寫此函數:
export const myStartup = (callback) => {
getJwtToken() // returns a Promise
.then((token) => { // token is available inside Promise's .then
callback(token);
})
}
現在,我們進入getJwtToken
內部,請記住,由於我們剛剛進行的更改,它必須返回Promise。
export const getJwtToken = () => {
let token = localStorage.getItem('myToken');
if (token == null)
token = getJwtTokenFromApi();
return token;
}
這是一個有趣的情況,因為getJwtToken
實現了分支行為,其中一個分支是同步的,而另一個分支不是同步的。 ( localStorage.getItem
塊,但getJwtTokenFromApi
是異步的。)處理此類情況的唯一方法是使整個函數異步:確保即使總是需要從同步源獲得數據,它也始終返回Promise。 。
由於localStorage.getItem
是同步的,因此,如果我們喜歡它給我們的值,則在返回該值之前將其包裝在Promise中。 否則,我們可以只返回getJwtTokenFromApi
返回的Promise:
export const getJwtToken = () => {
let token = localStorage.getItem('myToken')
if(token !== null)
return Promise.resolve(token);
return getJwtTokenFromApi();
}
現在,無論我們處於哪種情況,此函數都將返回一個包含令牌的Promise。
最后,我們進入getJwtTokenFromApi
,它做一些事情:
Request
如果所有這些事情都解決了,它想返回文本值。 但是這些任務中有一半是異步的,這又意味着整個功能必須異步。 這是您入門時的苗條版本:
export const getJwtTokenFromApi = () => {
var request = new Request('/api/token', {});
fetch(request)
.then((response) => {
response.text()
.then((token) => {
if(token.length > 0) {
localStorage.setItem('myToken', token);
return token;
} else {
return null;
}
})
})
}
這里最大的問題是您沒有返回fetch
。 這很重要,因為嵌套在內部的其他return
語句不適用於整個函數。 該功能將不會返回任何書面,雖然會執行XHR呼叫。 因此,第一個解決方法是return fetch
。
但是僅僅增加return
是不夠的。 為什么? 因為在.then
處理程序中,您想訪問響應的text
,但是該訪問本身是異步的。 當您使用的是 .then
訪問值(如token
),該值將里面默默地死去fetch.then
除非你也返回response.text()
確實,您需要的是:
return fetch(request)
.then((response) => {
return response.text()
.then((text) => {
if(text.length > 0) return text;
else return null
但是這段代碼是不必要的冗長,並且隨着嵌套的不斷深入,它向右爬行的方式使代碼難以閱讀或重新排序。 這些步驟是順序的,我們希望它們看起來像這樣:
STEP 1
STEP 2
STEP 3
(not)
STEP 1
STEP 2
STEP 3
因此,讓我們嘗試一些更苗條的方法:
return fetch(request) // step 1
.then((response) => response.text()) // step 2
.then((text) => text.length > 0 ? text : null) // step 3
該代碼更簡潔。 重新排序步驟或插入新步驟也更加容易。 當然,它並沒有完成將令牌存儲在localStorage中的重要工作,這就是為什么我們擁有更強大的最終版本的原因:
export const getJwtTokenFromApi = () => {
var request = new Request('/api/token', {
method: 'GET',
mode: 'cors',
credentials: 'include'
});
return fetch(request)
.then((response) => response.text())
.then((token) => {
if(token.length > 0) {
localStorage.setItem('myToken', token);
return token;
}
return null;
})
})
}
由於嵌套的Promises解析的方式,我們能夠對所有這些代碼進行拼合:當一個Promise包含另一個Promise(另一個)時,引擎將自動解開所有中間的Promise。 例如,這兩個片段產生相同的結果:
var x = Promise.resolve( Promise.resolve( Promise.resolve ( 10 )))
var y = Promise.resolve( 10 )
x
和y
都將像單個平坦的Promises一樣工作,解析為10
,這意味着我們可以將其放在任意一個之后:
.then((value) => {
// value === 10
})
這是最終的腳本:
export const getJwtTokenFromApi = () => {
var request = new Request('/api/token', {
method: 'GET',
mode: 'cors',
credentials: 'include'
});
return fetch(request)
.then((response) => response.text())
.then((token) => {
if(token.length > 0) {
localStorage.setItem('myToken', token);
return token;
}
return null;
})
})
}
export const getJwtToken = () => {
let token = localStorage.getItem('myToken')
if(token !== null)
return Promise.resolve(token);
return getJwtTokenFromApi();
}
export const myStartup = (callback) => {
getJwtToken()
.then((token) => {
callback(token);
})
}
還有一個問題: myStartup
異步?
使用上面的經驗法則,我們可以說,由於它包裝了異步行為,因此它本身就是異步的。 但是,此腳本混合了異步模式:承諾和回調。 我懷疑這是因為您一方面更熟悉節點樣式的回調,但是fetch
返回Promise,並且在實現過程中,這兩種方法有點“相遇” –或者更確切地說,是在模塊的API: myStartup
。 這是一個異步函數,但似乎對此並不滿意。
調用方調用myStartup
,將不會返回任何內容。 這很明顯,因為沒有return
語句。 但是,通過接受回調函數,您提供了一種機制,可以在所有潛在異步工作完成后向調用者發出信號,這意味着仍可以使用它。
除非支持節點樣式的回調模式很重要,否則我建議采取最后一步,使此模塊完全基於Promise:修改myStartup
,使其返回使用令牌解析的Promise。 由於上述解包行為,這是一個非常簡單的更改:
export const myStartup = () => {
return getJwtToken();
}
但是現在很明顯, myStartup
沒有為進程添加任何內容,因此您最好通過刪除函數並將getJwtToken
重命名為myStartup
來刪除包裝器。
我使用下面的代碼來完成此工作,但我認為它不是很優雅。
本質上,我將多個函數合並為一個,並添加了一個回調參數,以便創建一系列啟動函數。 如果沒有收到回調,我將簡單地返回令牌以使getJwtToken()
成為多用途函數,即調用它以獲取令牌或傳遞需要令牌的函數。
我真的很想擁有單獨的功能,以便並非所有關注點都在一個功能中。 另外,當我只需要獲取令牌時,對於那些具有回調參數的時間也不感到瘋狂。
我想發布代碼,以便獲得一些建議以使其更健壯和優雅。
export const getJwtToken = (callback) => {
// If token is already in localStorage, get it and return it.
const token = localStorage.getItem('myToken');
if (token != null)
return token;
// Token is not in localStorage. Get it from API
var request = new Request('/api/token', {
method: 'GET',
mode: 'cors',
credentials: 'include'
});
fetch(request)
.then((response) => {
response.text()
.then((token) => {
if (token.length > 0) {
// First, save it in localStorage
localStorage.setItem('myToken', token);
// If no callback function received, just return token
if (typeof callback == "undefined") {
return token;
} else {
callback(token);
}
}
})
})
.catch(err => {
});
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.