gpt4 book ai didi

javascript - 当一个同步函数突然需要一个异步值时,避免链式重构?

转载 作者:行者123 更新时间:2023-11-30 19:19:51 24 4
gpt4 key购买 nike

在使用 javascript 时,我一直遇到这样的情况,以前同步的代码突然需要一个只能异步获取的值。

例如,我正在处理一个 TamperMonkey 脚本,其中有一个函数对从 location.hash 解析的字符串进行操作。现在我想更改代码以通过使用 GM_getTab(callback) 接口(interface)在选项卡中启用跨 URL 更改的持久性。

因为我需要几个函数以不变的顺序执行,所以会发生链式 react ,因为我需要 await 这个值,我突然结束了调用过程中的几个函数重构 -堆栈到 async 函数中,直到我到达一个不再需要保证顺序的点。

然而,更重要的是,当忘记等待时,需要显式 await 的 promise 可能会导致意外行为:例如if(condition()) 可能突然总是评估为 true,而 'Hello '+getUserName() 可能突然导致 Hello [对象 promise ]

有什么方法可以避免这种“重构 hell ”吗?

简化示例

随后,我提供了一个非常简化的示例来说明所发生的事情:在调用堆栈中需要一个 await,同时需要保留执行顺序导致一直重构到事件回调 updateFromHash.

// -- Before
function updateFromHash(){
updateTextFromHash();
updateTitleFromText();
}

function updateTextFromHash(){
DISPLAY_AREA.innerText = getTextFromHash();
}

// -- After
async function updateFromHash(){
await updateTextFromHash();
updateTitleFromText();
}

async function updateTextFromHash(){
DISPLAY_AREA.innerText = getTextFromHash()
|| await new Promise((accept,reject)=>GM_getTab(accept));
}

在这种情况下,它相对简单,但我之前已经看到异步性在调用堆栈中更远的地方冒泡,并在错过 await 时导致意外行为。我见过的最糟糕的情况是,当我突然需要“DEBUG”标志来依赖于异步存储的用户设置时。

时间限制

正如@DavidSampson 所指出的,如果函数首先不依赖于可变全局状态,那么在示例中可能会更好。

然而,实际上代码是在时间限制下编写的,通常是由其他人编写的,然后你需要一个“小改动”——但如果那个小改动涉及以前同步函数中的异步数据,则最好将重构努力。解决方案需要现在起作用,清理设计问题可能要等到下一次项目 session 。

在给出的例子中,重构是可行的,因为它是一个小的、私有(private)的 TamperMonkey 脚本。它最终只是为了说明问题,但在商业项目场景下,在给定的项目范围内清理代码可能是不可行的。

最佳答案

这里有一些一般做法可以最大限度地减少您遇到的问题的影响。

一件事是尝试编写不那么依赖于首先以特定顺序发生的代码——它会导致很多令人头疼的事情,正如您在此处看到的。

async function updateFromHash(){
await updateTextFromHash();
updateTitleFromText();
}

在这里,您在某处更新全局变量中的一些文本(稍后会详细介绍),然后您调用一个函数来查看该变量,并根据该变量更新其他一些变量。显然,如果一个没有完成,另一个将无法正常工作。

但是,如果相反,您在一个地方检索异步数据,然后分派(dispatch)两个 update 调用并将它们所需的数据作为函数参数,那会怎样呢?此外,您可以使用 .then 链接来处理异步数据,而不必使函数异步。

async getHash(){
return new Promise((accept,reject)=>GM_getTab(accept))
}
function setText(text){
DISPLAY_AREA.innerText = text;
}
function setTitle(text){
// make some modifications to the 'text' variable

TITLE.innerText = myModifiedText // or whatever
}

function updateFromHash(){
getHash()
.then(text => {
setText(text);
setTitle(text);
// You could also call setTitle first, since they aren't dependent on each other
});
}

您可以做的另一项改进是,从广义上讲,尽可能保持函数纯净通常是个好主意——它们唯一应该修改的是您作为参数传入的内容,以及您期望的唯一效果他们返回的东西。由于各种原因,杂质肯定存在,但请尽量将它们留在代码的边缘。

所以在您的示例中,您有一个修改全局变量的函数,然后另一个函数可能会查看该变量,并根据该信息更改另一个变量。这将这两个变量隐式地绑定(bind)在一起,只是粗心的观察者不清楚为什么会这样,因此它使跟踪错误变得更加困难。处理您的情况的一种方法:

function createTitleFromText(text){
// modify the text passed in to get the title you want
return myModifiedText;
// this function is pure
}

function updateContent(text){
// This is now the ONLY function that modifies state
// It also has nothing to do with *how* the text is retrieved
TITLE_EL.innerText = createTitleFromText(text);
DISPLAY_AREA.innerText = text;
}

async function getTextFromHash(){
return new Promise((accept,reject)=>GM_getTab(accept))
}

// Then, somewhere else in your code
updateContent(await getTextFromHash());

为此添加一些面向对象的内容可能是个好主意,以使其更清楚什么拥有什么,并为您处理一些排序。

class Content {
constructor(textEl, titleEl){
this.textEl = textEl;
this.titleEl = titleEl;
}
static createTitleFromText(text){
//make modifications
return myTitle;
}
update(text){
this.textEl.innerText = text;
this.titleEl.innerText = Content.createTitleFromText(text);
}
}

let myContent = new Content(DISPLAY_AREA, TITLE_EL);

// Later

myContent.update(await getTextFromHash());

方法没有对错之分,但这些是您可以尝试的一些想法。

至于阻止异步性冒泡,使用 .then 链接可能是最好的选择。以您的原始示例为例:

// -- After
function updateFromHash(){
updateTextFromHash()
.then(updateTitleFromText);

}

async function updateTextFromHash(){
DISPLAY_AREA.innerText = getTextFromHash()
|| await new Promise((accept,reject)=>GM_getTab(accept));
}

现在 updateFromHash 可以保持同步,但请记住 updateFromHash 完成并不意味着 updateTitleFromText 完成,甚至 updateTextFromHash 完成。因此,您需要尽可能地让异步性冒泡,以处理任何有序的效果。

不幸的是,由于 JS 引擎的单线程特性,没有办法同步 await——如果一个同步函数正在等待某事完成,其他任何东西都无法运行。

虽然对于特定情况,您可能能够同步复制该功能,但它也将涉及大量重构。

例如,您可以定义属性 DISPLAY_AREA.isValid = true,然后在 updateTextFromHash

DISPLAY_AREA.isValid = false;
DISPLAY_AREA.innerText = getTextFromHash()
|| await new Promise((accept,reject)=>GM_getTab(accept));
DISPLAY_AREA.isValid = true;

然后,在任何需要 DISPLAY_AREA.innerText 的代码中,首先检查该数据是否有效,如果无效,则 setTimeout 一段时间的时间,然后再次检查。

或者,您也可以定义一个DISPLAY_AREA.queuedActions = [],然后您的其他函数可以检查DISPLAY_AREA 的有效性,如果为假,则添加一个回调到 queuedActions。然后 updateTextFromHash 看起来像:

DISPLAY_AREA.isValid = false;
DISPLAY_AREA.innerText = getTextFromHash()
|| await new Promise((accept,reject)=>GM_getTab(accept));
for(var cb of DISPLAY_AREA.queuedActions){
cb()
}
DISPLAY_AREA.isValid = true;

当然,在大多数情况下,这只会导致您的无效逻辑像 async/await 那样冒泡,但在某些情况下,它可能会干净利落。

关于javascript - 当一个同步函数突然需要一个异步值时,避免链式重构?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57599578/

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