我遇到了一些异步函数的问题,它们的行为似乎不正常。下面的脚本在少量元素上触发forEach
,并使用原始元素innerHTML
的用户ID向API执行AJAX请求以获取用户名,并更新文本。
问题是关于users
对象,我希望将以前提取的用户名存储在该对象中,并绕过对同一用户进行后续AJAX调用的需要。我已经尝试过使forEach
回调异步并等待它,以及window.onload
,但脚本似乎总是触发所有AJAX调用,而不是像我预期的那样等待。
// The object that stores user names that are fetched from API
const users = {};
const getUserName = async (userId) => {
// Return the saved user name if it is stored already
if (users[userId]) {
return users[userId];
}
// Calls the API to get the name of the user
const response = await fetch(
`/user/${userId}/`,
);
const userName = await response.text();
// Store the user name to prevent duplicate calls
users[userId] = userName;
return userName;
};
const convertNameNode = async (nameNode) => {
const userId = nameNode.innerHTML;
const userName = await getUserName(userId);
// Replace the text in the DOM, e.g. from 1 to 'Steve'
nameNode.innerHTML = userName;
};
// On load, trigger conversion of user IDs to user names
window.onload = () => {
document.querySelectorAll('.userId').forEach(node => {
convertNameNode(node);
});
};
由于fetch()
是异步的(读作:"由于fetch()
返回一个promise">),但您试图将fetch()
的响应文本分配给缓存对象,因此在服务器响应之前,缓存将为空-这将在.forEach()
循环完成很久之后发生。
因此,您需要存储promise本身,而不是将fetch promise的响应文本存储在缓存中:
const users = {};
const getUserName = (userId) => {
if (!users[userId]) {
console.log(`requesting name for ID ${userId}...`);
users[userId] = fetch(`/user/${userId}/`).then(r => r.text());
}
return users[userId];
};
const convertNameNode = async (nameNode) => {
nameNode.innerHTML = await getUserName(nameNode.innerHTML);
};
const test = () => {
document.querySelectorAll('.userId').forEach(convertNameNode);
};
test();
// ----------------------------------------------------------------
// mockup fetch
function randInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function fetch(url) {
const userId = url.split('/')[2];
return new Promise(resolve => setTimeout(() => resolve({
text: () => "Username for " + userId
}), randInt(250, 1000)));
}
<div class="userId">1</div>
<div class="userId">2</div>
<div class="userId">3</div>
<div class="userId">1</div>
<div class="userId">2</div>
<div class="userId">3</div>
<div class="userId">4</div>
票据
-
getUserName()
不再需要是async
,因为它不需要等待任何东西。它创建承诺并返回承诺——等待是调用者的工作。 -
我使用
fetch().then(r => r.text());
将fetch promise的结果转换为字符串
在fetch中,响应.text()
只能读取一次,因为它是一个流,尝试读取蒸汽两次将导致错误。但是promise会无限期地记住它们的结果值,即同一个promise可以用相同的结果多次await
。因此,我使用.then()
将整个promise结果更改为.text()
的值。通过这种方式,await getUserName(1)
将始终生成一个字符串——这正是我从缓存中需要的。 -
CCD_ 18可降为
const getUserName = uid => users[uid] || users[uid] = fetch(`/user/${uid}/`).then(r => r.text());
问题是您没有等待fetch操作完成来启动新操作。
以下是我的做法。
解决方案1等待并调用每个值。
// On load, trigger conversion of user IDs to user names
window.onload = async () => {
var nodes = document.querySelectorAll('.userId');
for(var i=0; i< nodes.length; i++){
await convertNameNode(nodes[i]); // wait here, before processing the next node
}
解决方案2使用缓存同时调用多标签值除了必须在onLoad 上进行一些更改之外,您当前的代码应该可以工作
const convertNameNode = async (nameNode, nodeList) => {
const userId = nameNode.innerHTML;
const userName = await getUserName(userId);
// Replace the text in the DOM, e.g. from 1 to 'Steve' for all userId with 1
nodes.filter(x=> x.innerHTML === userId).forEach(x=> {
x.innerHTML = userName;
});
};
// On load, trigger conversion of user IDs to user names
window.onload = () => {
var nodes = document.querySelectorAll('.userId');
// loop only throw a distinct userId
nodes.reduce((acc, value)=> {
if (!acc.find(x=> x.innerHTML == value.innerHTML))
acc.push(value);
return acc;
},[]).forEach(node => {
convertNameNode(node, nodes);
});