简体   繁体   English

如何推迟执行代码,直到获取返回值

[英]How do I defer the execution of code until fetch returns value

I created the following two functions to get a JWT Token from my backend and store it in localStorage . 我创建了以下两个函数来从后端获取JWT Token并将其存储在localStorage The logic seems pretty simple but it's not working because before the value of my token is resolved, the code continues executing. 逻辑看似很简单,但无法正常工作,因为在解析令牌的值之前,代码将继续执行。 As a result, functions in the chain that need the token don't get it. 结果,链中需要令牌的功能无法获得令牌。 Ultimately, it does get the token and stores it in localStorage but by that time all my functions in the start up chain fail to get a valid token. 最终,它确实获得了令牌并将其存储在localStorage但是到那时,我在启动链中的所有函数都无法获得有效的令牌。

I'm doing all of this at the very beginning as my React app is starting up. 在我的React应用程序启动时,我从一开始就做所有这一切。 So the idea is to get the token and then execute the remaining start up steps. 因此,想法是获取令牌,然后执行其余的启动步骤。

So, I'm calling myStartup() function at the entry point to my app. 因此,我在应用程序的入口点调用myStartup()函数。 I do this as the first step. 我这样做是第一步。 From there, the remaining start up functions are passed as a callback. 从那里,其余的启动函数作为回调传递。

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

What ends up happeningis that the calling function that needs the token is not deferred until the token is received so it ends up receiving undefined . 最终发生的事情是,需要令牌的调用函数直到收到令牌后才被推迟,因此最终接收到undefined

How do I make sure I defer the execution of my function that needs the token until I have a legit token -- or at least a null value which would mean my API call failed? 如何确保我将需要令牌的函数的执行推迟到拥有合法令牌之前-或至少为null值,这意味着我的API调用失败?

Your function getJwtToken should return the promise: 您的函数getJwtToken应该返回promise:

export const getJwtToken = () => {   
  let token = localStorage.getItem('myToken');
  return token ? Promise.resolve(token) : getJwtTokenFromApi(storeToken) 
}

in your caller, the token will be wrapped in the inside returned promise: 在您的呼叫者中,令牌将被包装在返回的内部Promise中:

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

The fundamental problem is that you're not handling all the async stuff as though it's async. 根本的问题是您没有像异步一样处理所有异步内容。 This is a reasonably complicated workflow, with blocking and non-blocking tasks intermixed, which means that we need to apply asynchronous patterns throughout. 这是一个相当复杂的工作流程,混杂了阻塞和非阻塞任务,这意味着我们需要始终应用异步模式。 Let's step through the script one function at a time. 让我们一次一步地完成脚本的一个功能。


This appears to be the script's entry point: 这似乎是脚本的入口点:

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

It won't work, because getJwtToken is async, which means that its value will not be available to callback on the next line. 由于getJwtToken是异步的,因此它将不起作用,这意味着其值将不可用于下一行的callback

How do we know that getJwtToken is async? 我们如何知道getJwtToken是异步的? Because getJwtToken invokes getJwtTokenFromApi , which invokes fetch (which the spec tells us is async). 因为getJwtToken调用getJwtTokenFromApi ,后者调用fetch (规范告诉我们异步)。 Since getJwtToken wraps async behavior, it is itself async. 由于getJwtToken包装了异步行为,因此它本身就是异步的。

Since getJwtToken is async, we know that token is not going to be available on the second line when callback needs it. 由于getJwtToken是异步的,因此我们知道,当callback需要它时,第二行将无法使用token In fact, token will never be available in that scope, because getJwtToken returns a Promise, whose resolution value will only be available inside a .then handler. 实际上, token在该范围内将永远不可用,因为getJwtToken返回一个Promise,其解析值仅在.then处理程序中可用。 So, step 1 is to rewrite this function: 因此,第一步是重写此函数:

export const myStartup = (callback) => {
    getJwtToken() // returns a Promise
    .then((token) => { // token is available inside Promise's .then
        callback(token);
    })
}

Now we look inside getJwtToken , bearing in mind that it must return a Promise because of the changes we just made. 现在,我们进入getJwtToken内部,请记住,由于我们刚刚进行的更改,它必须返回Promise。

export const getJwtToken = () => {
    let token = localStorage.getItem('myToken');
    if (token == null)
        token = getJwtTokenFromApi();
    return token;
}

This is an interesting case, because getJwtToken implements branching behavior, one branch of which is synchronous, and the other not. 这是一个有趣的情况,因为getJwtToken实现了分支行为,其中一个分支是同步的,而另一个分支不是同步的。 ( localStorage.getItem blocks, but getJwtTokenFromApi is async.) The only way to handle cases like this is to make the entire function async: to make sure that it always returns a Promise, even if the data it needs is available from a sync source. localStorage.getItem块,但getJwtTokenFromApi是异步的。)处理此类情况的唯一方法是使整个函数异步:确保即使总是需要从同步源获得数据,它也始终返回Promise。 。

Since localStorage.getItem is synchronous, if we like the value it gives us, we wrap that value in a Promise before returning it. 由于localStorage.getItem是同步的,因此,如果我们喜欢它给我们的值,则在返回该值之前将其包装在Promise中。 Otherwise, we can just return the Promise returned by getJwtTokenFromApi : 否则,我们可以只返回getJwtTokenFromApi返回的Promise:

export const getJwtToken = () => {
    let token = localStorage.getItem('myToken')

    if(token !== null)
        return Promise.resolve(token);

    return getJwtTokenFromApi();
}

Now, no matter which scenario we find ourselves in, this function will return a Promise that contains a token. 现在,无论我们处于哪种情况,此函数都将返回一个包含令牌的Promise。

Finally, we get to getJwtTokenFromApi , which does a few things: 最后,我们进入getJwtTokenFromApi ,它做一些事情:

  • it constructs a Request 它构造一个Request
  • it executes a request (async) 它执行一个请求(异步)
  • if successful, it converts the response to text (async) 如果成功,它将响应转换为文本(异步)
  • it inspects the text 它检查文本

If all those things work out, it wants to return the text value. 如果所有这些事情都解决了,它想返回文本值。 But half of those tasks are async, which again means that the entire function must become async. 但是这些任务中有一半是异步的,这又意味着整个功能必须异步。 Here's a slimmer version of what you started with: 这是您入门时的苗条版本:

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

The biggest problem here is that you're not returning the fetch . 这里最大的问题是您没有返回fetch This is important, because the other return statements nested inside don't apply to the overall function. 这很重要,因为嵌套在内部的其他return语句不适用于整个函数。 This function will not return anything as written, although it will perform an XHR call. 该功能将不会返回任何书面,虽然执行XHR呼叫。 So, the first fix is to return fetch . 因此,第一个解决方法是return fetch

But just adding that return isn't enough. 但是仅仅增加return是不够的。 Why? 为什么? Because within the .then handler, you want to access the text of the response, but that access is itself async. 因为在.then处理程序中,您想访问响应的text ,但是该访问本身是异步的。 While you are using a .then to access the value (as token ), that value will die silently inside fetch.then unless you also return response.text() . 当您使用的 .then访问值(如token ),该值将里面默默地死去fetch.then除非你也返回response.text() Really, what you need is this: 确实,您需要的是:

return fetch(request)
.then((response) => {
    return response.text()
    .then((text) => {
        if(text.length > 0) return text;
        else return null

But this code is needlessly verbose, and the way it creeps to the right with deeper and deeper nesting makes for code that is hard to read or re-order. 但是这段代码是不必要的冗长,并且随着嵌套的不断深入,它向右爬行的方式使代码难以阅读或重新排序。 These steps are sequential, and we want them to look that way: 这些步骤是顺序的,我们希望它们看起来像这样:

STEP 1
STEP 2
STEP 3

(not)
STEP 1
    STEP 2
        STEP 3

So, let's try something slimmer: 因此,让我们尝试一些更苗条的方法:

return fetch(request)                          // step 1
.then((response) => response.text())           // step 2
.then((text) => text.length > 0 ? text : null) // step 3

This code is flatter and slimmer. 该代码更简洁。 It's also easier to re-order the steps or insert new ones. 重新排序步骤或插入新步骤也更加容易。 Of course, it doesn't do the important work of storing the token in localStorage, which is why we have the slightly beefier final version: 当然,它并没有完成将令牌存储在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;
     })
    })
}

We're able to flatten all this code because of the way nested Promises resolve: when one Promise contains another Promise (and another, etc.), the engine will automatically unwrap all of the intermediate promises. 由于嵌套的Promises解析的方式,我们能够对所有这些代码进行拼合:当一个Promise包含另一个Promise(另一个)时,引擎将自动解开所有中间的Promise。 As an example, these two snippets produce identical results: 例如,这两个片段产生相同的结果:

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

Both x and y will act like single, flat Promises that resolve to 10 , which means we can put this after either one: xy都将像单个平坦的Promises一样工作,解析为10 ,这意味着我们可以将其放在任意一个之后:

.then((value) => {
    // value === 10
})

Here's the final script: 这是最终的脚本:

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

One more question: is myStartup async or not? 还有一个问题: myStartup异步?

Using the rule of thumb from above, we'd say that since it wraps async behavior, it is itself async. 使用上面的经验法则,我们可以说,由于它包装了异步行为,因此它本身就是异步的。 However, this script mixes async patterns: both Promises & callbacks. 但是,此脚本混合了异步模式:承诺和回调。 I suspect this is because you are more familiar with node-style callbacks one the one hand, but fetch returns a Promise, and during implementation those two approaches kind of "meet in the middle" -- or rather, at the module's API: myStartup . 我怀疑这是因为您一方面更熟悉节点样式的回调,但是fetch返回Promise,并且在实现过程中,这两种方法有点“相遇” –或者更确切地说,是在模块的API: myStartup It's an async function, but it doesn't seem comfortable with that fact. 这是一个异步函数,但似乎对此并不满意。

When a caller invokes myStartup , it will return nothing. 调用方调用myStartup ,将不会返回任何内容。 That much is obvious because there is no return statement. 这很明显,因为没有return语句。 But, by accepting a callback function, you've provided a mechanism to signal callers once all the potentially-async work is complete, which means it can still be used. 但是,通过接受回调函数,您提供了一种机制,可以在所有潜在异步工作完成后向调用者发出信号,这意味着仍可以使用它。

Unless it's important to support the node-style callback pattern, I'd recommend taking the final step to make this module thoroughly Promise-based: modify myStartup so that it returns a Promise that resolves with the token. 除非支持节点样式的回调模式很重要,否则我建议采取最后一步,使此模块完全基于Promise:修改myStartup ,使其返回使用令牌解析的Promise。 Because of the aforementioned unwrapping behavior, this is an extremely simple change: 由于上述解包行为,这是一个非常简单的更改:

export const myStartup = () => {
    return getJwtToken();
}

But now it's obvious that myStartup adds nothing to the process, so you might as well remove the wrapper by deleting the function and renaming getJwtToken to myStartup . 但是现在很明显, myStartup没有为进程添加任何内容,因此您最好通过删除函数并将getJwtToken重命名为myStartup来删除包装器。

I got this working with the following code but I don't think it's very elegant. 我使用下面的代码来完成此工作,但我认为它不是很优雅。

Essentially, I combined multiple functions into one and added a callback parameter so that I can create a chain of start up functions. 本质上,我将多个函数合并为一个,并添加了一个回调参数,以便创建一系列启动函数。 If no callback is received, I simply return the token in order to make the getJwtToken() a multi purpose function ie either call it to get the token or pass a function that expects the token. 如果没有收到回调,我将简单地返回令牌以使getJwtToken()成为多用途函数,即调用它以获取令牌或传递需要令牌的函数。

I really would like to have separate functions so that not all concerns are in one function. 我真的很想拥有单独的功能,以便并非所有关注点都在一个功能中。 Also, not crazy about having callback parameter for those times when I need to just get the token. 另外,当我只需要获取令牌时,对于那些具有回调参数的时间也不感到疯狂。

I wanted to post the code so that I can get some suggestions to make it more robust and elegant. 我想发布代码,以便获得一些建议以使其更健壮和优雅。

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