async 函数会在开始时正确更新迭代器,但不会在结束时正确更新迭代器
async function does update iterator correctly at the begining but not at the end
在下面的代码中,我使用 CSS DOM,这可能计算量很大。这显然是访问 :after
选择器所必需的。 renderItem
方法将向 DOM 添加一个新项目(包括它在元素之后),这就是我使用 async
函数和 await
的原因 return 在 loadFromStorage
.
的每次迭代中
然而,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('//')
returns 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: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
调用 resolve 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
的值,这样在该函数运行期间该值不会改变,但是对我来说似乎是一个非常 hacky 的解决方法。
在下面的代码中,我使用 CSS DOM,这可能计算量很大。这显然是访问 :after
选择器所必需的。 renderItem
方法将向 DOM 添加一个新项目(包括它在元素之后),这就是我使用 async
函数和 await
的原因 return 在 loadFromStorage
.
然而,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('//')
returns 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: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
调用 resolve 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
的值,这样在该函数运行期间该值不会改变,但是对我来说似乎是一个非常 hacky 的解决方法。