在下面的代码中,我使用的是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循环,并修改renderItem
以n
作为自变量
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
的值,这样在该函数的持续时间内,该值不会改变,但对我来说,这似乎是一个非常棘手的解决方法