簡體   English   中英

如何推遲執行代碼,直到獲取返回值

[英]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 )

xy都將像單個平坦的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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM