gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-12-01 02:33:32 24 4
gpt4 key购买 nike

我创建了以下两个函数以从后端获取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调用失败?

最佳答案

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



这似乎是脚本的入口点:

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


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

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

由于 getJwtToken是异步的,我们知道 token在需要 callback的第二行将不可用。实际上, 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都将像解析为 10的单个,平坦的Promises一样工作,这意味着我们可以将其放在任何一个之后:

.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来删除包装器。

关于javascript - 如何推迟执行代码,直到获取返回值,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48130445/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com