在获取返回值之前,如何推迟执行代码

时间:2018-01-06 18:20:05

标签: javascript fetch

我创建了以下两个函数,以便从后端获取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

我如何确保在我拥有合法令牌之前推迟执行需要令牌的函数 - 或者至少是一个空值,这意味着我的API调用失败了?

3 个答案:

答案 0 :(得分:1)

您的函数getJwtToken应该返回承诺:

export const getJwtToken = () => {   
  let token = localStorage.getItem('myToken');
  return token ? Promise.resolve(token) : getJwtTokenFromApi(storeToken) 
}
在你的调用者中,令牌将包含在内部返回的promise中:

getJwtToken().then(token => doSomething(token))

答案 1 :(得分:1)

根本问题在于你没有处理所有异步内容,就好像它是异步的一样。这是一个相当复杂的工作流程,其中混合了阻塞和非阻塞任务,这意味着我们需要在整个过程中应用异步模式。让我们一次逐步完成脚本。

这似乎是剧本的切入点:

export const myStartup = (callback) => {
    const token = getJwtToken();
    callback(token);
}

它无法正常工作,因为getJwtToken是异步的,这意味着它的值将无法在下一行callback上使用。

我们怎么知道getJwtToken是异步的?因为getJwtToken调用getJwtTokenFromApi,它会调用fetch(规范告诉我们这是异步的)。由于getJwtToken包含异步行为,因此它本身就是异步。

由于getJwtToken是异步的,我们知道当token需要callback时,token将无法在第二行显示。事实上,getJwtToken 永远不会在该范围内可用,因为.then返回一个Promise,其分辨率值仅在export const myStartup = (callback) => { getJwtToken() // returns a Promise .then((token) => { // token is available inside Promise's .then callback(token); }) } 处理程序中可用。所以,第1步是重写这个功能:

getJwtToken

现在我们看看export const getJwtToken = () => { let token = localStorage.getItem('myToken'); if (token == null) token = getJwtTokenFromApi(); return token; } 内部,记住它必须返回一个Promise,因为我们刚刚做出了改变。

getJwtToken

这是一个有趣的案例,因为localStorage.getItem实现了分支行为,其中一个分支是同步的,另一个不是。 (getJwtTokenFromApi阻止,但localStorage.getItem是异步的。)处理这种情况的唯一方法是使整个函数异步:确保它总是返回一个Promise即使它所需的数据可从同步源获得。

由于getJwtTokenFromApi是同步的,如果我们喜欢它给我们的值,我们在返回之前将该值包装在Promise中。否则,我们只能返回export const getJwtToken = () => { let token = localStorage.getItem('myToken') if(token !== null) return Promise.resolve(token); return getJwtTokenFromApi(); } 返回的Promise:

getJwtTokenFromApi

现在,无论我们发现自己处于哪种场景,此函数都会返回包含令牌的Promise。

最后,我们进入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; } }) }) }
  • 执行请求(async)
  • 如果成功,则将响应转换为文本(async)
  • 检查文本

如果所有这些都成功,它想要返回文本值。但是这些任务中有一半是异步的,这也意味着整个函数必须变为异步。这是您开始使用的更轻薄的版本:

fetch

这里最大的问题是你没有归还return。这很重要,因为嵌套在其中的其他return fetch语句不适用于整体功能。尽管 将执行XHR调用,但此函数不会返回任何内容。因此,第一个修复是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

所以,让我们尝试一些苗条的东西:

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;
     })
    })
}

这段代码更平坦,更苗条。它也更容易重新排序或插入新的步骤。当然,它并没有完成将令牌存储在localStorage中的重要工作,这就是为什么我们有一个稍微强大的最终版本:

var x = Promise.resolve( Promise.resolve( Promise.resolve ( 10 )))
var y = Promise.resolve( 10 )

由于嵌套Promise解析的方式,我们能够展平所有这些代码:当一个Promise包含另一个Promise(和另一个Promise等)时,引擎将自动解包所有中间承诺。例如,这两个片段产生相同的结果:

x

y10都会像单一,平坦的Promise一样解析为.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&回调。我怀疑这是因为你一方面更熟悉节点式回调,但是myStartup返回一个Promise,并且在实现过程中这两种方法在中间相遇" - 或者更确切地说,在模块的API上:myStartup。它是一个异步功能,但它对这个事实似乎并不舒服。

当呼叫者调用return时,它将不返回任何内容。这很明显是因为没有myStartup陈述。但是,通过接受回调函数,您已经提供了一种机制,可以在所有潜在的异步工作完成后向呼叫者发出信号,这意味着它仍然可以使用。

除非支持节点式回调模式很重要,否则我建议采取最后一步,使该模块完全基于Promise:修改export const myStartup = () => { return getJwtToken(); } ,使其返回Promise用令牌解析。由于上述展开行为,这是一个非常简单的变化:

myStartup

但现在显而易见的是getJwtToken没有为流程添加任何内容,因此您也可以通过删除该功能并将myStartup重命名为{{1}}来删除包装器。 / p>

答案 2 :(得分:0)

我使用以下代码进行了此操作,但我不认为它非常优雅。

基本上,我将多个函数合并为一个并添加了一个回调参数,以便我可以创建一系列启动函数。如果没有收到回调,我只需返回令牌,以使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 => {
        });
}