如何延迟代码执行,直到获取返回值 [英] How do I defer the execution of code until fetch returns value

查看:94
本文介绍了如何延迟代码执行,直到获取返回值的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我创建了以下两个函数来从后端获取JWT Token并将其存储在localStorage中.逻辑看似很简单,但却无法正常工作,因为在解析令牌的值之前,代码将继续执行.结果,链中需要令牌的功能无法获得它.最终,它确实获得了令牌并将其存储在localStorage中,但是到那时,我在启动链中的所有函数都无法获得有效的令牌.

I created the following two functions to get a JWT Token from my backend and store it in 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.

在我的React应用程序启动时,我从一开始就做所有这一切.因此,想法是获取令牌,然后执行其余的启动步骤.

I'm doing all of this at the very beginning as my React app is starting up. So the idea is to get the token and then execute the remaining start up steps.

因此,我在应用程序的入口点调用myStartup()函数.我这样做是第一步.从那里开始,其余的启动函数将作为回调传递.

So, I'm calling myStartup() function at the entry point to my app. 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);
}

最终发生的情况是,需要令牌的调用函数直到收到令牌后才被推迟,因此最终接收到undefined.

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.

如何确保将需要令牌的函数的执行推迟到拥有合法令牌之前-或至少为null值,这意味着我的API调用失败了?

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?

推荐答案

基本问题是您没有像处理异步一样处理所有异步内容.这是一个相当复杂的工作流程,混杂了阻塞和非阻塞任务,这意味着我们需要始终应用异步模式.让我们一次遍历脚本一个功能.

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

它不起作用,因为getJwtToken是异步的,这意味着它的值将对下一行的callback不可用.

It won't work, because getJwtToken is async, which means that its value will not be available to callback on the next line.

我们如何知道getJwtToken是异步的?因为getJwtToken调用getJwtTokenFromApi,而后者又调用fetch(规范告诉我们是异步的).由于getJwtToken包装了异步行为,因此它本身就是异步的.

How do we know that getJwtToken is async? Because getJwtToken invokes getJwtTokenFromApi, which invokes fetch (which the spec tells us is async). Since getJwtToken wraps async behavior, it is itself async.

由于getJwtToken是异步的,所以我们知道token在需要callback时在第二行将不可用.实际上,token永远不会在该范围内可用,因为getJwtToken返回一个Promise,其分辨率值仅在.then处理程序中可用.因此,第一步是重写此函数:

Since getJwtToken is async, we know that token is not going to be available on the second line when callback needs it. 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. 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);
    })
}

现在,我们要进入getJwtToken内部,请记住,由于我们刚刚进行的更改,它必须返回Promise.

Now we look inside getJwtToken, bearing in mind that it must return a Promise because of the changes we just made.

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

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

This is an interesting case, because getJwtToken implements branching behavior, one branch of which is synchronous, and the other not. (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是同步的,因此,如果我们喜欢它给我们的值,则在返回值之前将其包装在Promise中.否则,我们只能返回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. Otherwise, we can just return the Promise returned by getJwtTokenFromApi:

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

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

    return getJwtTokenFromApi();
}

现在,无论我们处于哪种情况,此函数都将返回一个包含令牌的Promise.

Now, no matter which scenario we find ourselves in, this function will return a Promise that contains a token.

最后,我们进入getJwtTokenFromApi,它做了几件事:

Finally, we get to getJwtTokenFromApi, which does a few things:

  • 它构造一个Request
  • 它执行一个请求(异步)
  • 如果成功,它将响应转换为文本(异步)
  • 它检查文本
  • it constructs a 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;
            }
         })
    })
}

这里最大的问题是您没有返回fetch.这很重要,因为嵌套在其中的其他return语句不适用于整个功能.尽管此函数执行XHR调用,但它不会返回任何书面内容.因此,第一个解决方法是对return fetch.

The biggest problem here is that you're not returning the fetch. This is important, because the other return statements nested inside don't apply to the overall function. This function will not return anything as written, although it will perform an XHR call. So, the first fix is to return fetch.

但是仅添加return是不够的.为什么?因为在.then处理程序中,您想访问响应的text,但是该访问本身是异步的.当您使用.then访问 值时(作为token),除非您也返回response.text(),否则该值将在fetch.then内部默默消失.真的,您需要的是这样:

But just adding that return isn't enough. Why? Because within the .then handler, you want to access the text of the response, but that access is itself async. 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(). 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

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

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:

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.例如,这两个代码片段会产生相同的结果:

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. As an example, these two snippets produce identical results:

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

xy都将像解析为10的单个,平坦的承诺一样起作用,这意味着我们可以将其放在任何一个之后:

Both x and y will act like single, flat Promises that resolve to 10, which means we can put this after either one:

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


另一个问题:myStartup是否异步?


One more question: is myStartup async or not?

使用经验法则,我们可以说由于它包装了异步行为,因此它本身就是异步的.但是,此脚本混合了异步模式:Promises&回调.我怀疑这是因为您一方面更熟悉节点样式的回调,但是fetch返回一个Promise,并且在实现过程中,这两种方法相约在中间"-或更确切地说,是在模块的API上: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. It's an async function, but it doesn't seem comfortable with that fact.

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

When a caller invokes myStartup, it will return nothing. That much is obvious because there is no return statement. 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.

除非支持节点样式的回调模式很重要,否则我建议您采取最后一步,使此模块完全基于Promise:修改myStartup,以便它返回一个使用令牌解析的Promise.由于上述展开行为,这是一个非常简单的更改:

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. Because of the aforementioned unwrapping behavior, this is an extremely simple change:

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

但是现在很明显myStartup没有为该过程添加任何内容,因此您最好通过删除函数并将getJwtToken重命名为myStartup来删除包装器.

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.

这篇关于如何延迟代码执行,直到获取返回值的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆