async函数在开始时确实正确地更新了迭代器,但在结束时没有



在下面的代码中,我使用的是CSS DOM,它可能计算量很大。这显然是访问:after选择器所必需的。renderItem方法将向DOM添加一个新项(包括它的after元素),这就是为什么我在loadFromStorage内部的每次迭代中都使用async函数和await来返回它的原因。

然而,await似乎无法正常工作,或者renderItem函数内部发生了一些奇怪的事情。n迭代器在函数开始时被正确更新(项目被正确地呈现到屏幕上,第一个console.debug以正确的顺序打印正确的值),但在底部,第二个打印的值总是最后一个迭代值(在我的情况下是4,因为我正试图从本地存储呈现4个项目),getCSSRule方法得到了错误的数字。

let books = []
let n = 0
const renderItem = async (entry, direction = 1) => {
const li = document.createElement('li')
const ul = document.querySelector('ul')
li.classList.add('item')
n += 1
console.debug(`iter: ${n}`)
li.id = (`item${n}`)
await addCSSRule(`#item${n}:after`)
li.innerText = entry.slice(0, entry.length - 13)
if (direction === 1)
ul.appendChild(li)
else
ul.insertBefore(li, ul.firstChild)
console.debug(`iter: ${n}`)
const s = await getCSSRule(`#item${n}::after`).catch(() => {
console.debug(`Failed to find ':after' selector of 'item${n}'`)
return false
})
s.style.content = """+ entry.slice(entry.length - 13, entry.length) +"""
return true
}
const loadFromStorage = () => {
books = localStorage.getItem('books').split('//')
books.forEach(async (entry) => {
await renderItem(entry)
})
}
...

控制台结果(考虑localStorage.getItem('books').split('//')返回4项):

iter: 1
iter: 2
iter: 3
iter: 4
iter: 4 // Printed x4

我还试图将这个renderItem方法传递给Promise对象内部的await,这给了我相同的结果。此外,当我在函数末尾更新n迭代器时,同样的事情也会发生,但在它的开头

很抱歉,如果我使用的一些术语在JavaScript的上下文中不正确,我已经很多年没有使用这种语言了,目前我正在努力学习。

这里的关键问题是将异步函数传递给forEach,因此即使您在await的主体中,forEach也不会等待函数本身。为了说明这里事件的顺序,假设你有4本书A、B、C、D。你的执行看起来像这样。

  • renderItem(A)
  • n += 1(n现在为1)
  • console.log(n)(日志1)
  • await addCSSRule(`#item${1}:after`)(这是一个真正异步的事件,因此这释放了事件循环来处理其他事情,即forEach中的下一个元素)
  • renderItem(B)
  • n += 1(2)
  • console.log(n)(日志2)
  • renderItem(C)。。。n += 1(3)。。。await addCSSRule
  • renderItem(D)。。。n += 1(4)。。。await addCSSRule

然后每当addCSSRule调用时,无论您在哪个调用中,解析n都将始终为4

解决方案

使用for await...of循环而不是Array.prototype.forEach

for await (const entry of books) {
await renderItem(entry);
}

或传统的for循环,并修改renderItemn作为自变量

for (let i = 0; i < books.length; i++) {
renderItem(books[i], i+1);
// we don't need to await in this case, and we add 1 to i so that the 'n' value is 1-indexed to match your current behaviour.
}

我更喜欢后一种选择,因为它是避免可变全局状态(n变量)的最佳实践,因为它可能会导致控制流和问题的混乱,就像您遇到的问题一样。

另一种选择是在renderItem内递增局部变量后,将其设置为n的值,这样在该函数的持续时间内,该值不会改变,但对我来说,这似乎是一个非常棘手的解决方法

最新更新