通过异步 AJAX 调用转换元素文本

我遇到了一些似乎运行不正常的异步函数的问题。下面的脚本在少量元素上触发 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);
  });
};

zyr137583910 回答:通过异步 AJAX 调用转换元素文本

由于 fetch() 是异步的(阅读:“Since fetch() 返回承诺”),但您尝试将 fetch() 的响应文本分配给您的缓存对象,您的缓存将是空的,直到服务器响应 - 这将在您的 .forEach() 循环完成后很久发生。

因此,不是将 fetch 承诺的响应文本存储在缓存中,而是需要存储承诺本身:

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 中,响应 .text() 只能被读取一次,因为它是一个流,尝试读取一个流两次会导致错误。但是 Promise 会无限期地记住它们的结果值,即同一个 Promise 可以多次await获得相同的结果。因此,我使用 .then() 将总体承诺结果更改为 .text() 的值。这样 await getUserName(1) 将始终生成一个字符串 - 正是我需要从缓存中获取的内容。

  • getUserName() 可以减少为

    const getUserName = uid => users[uid] || users[uid] = fetch(`/user/${uid}/`).then(r => r.text());
    
,

问题在于您没有等待获取操作完成以启动新操作。

这就是我的做法。

解决方案 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
  }

方案二与缓存同时调用多标签值 您当前的代码应该可以工作,但必须对 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);
  });

本文链接:https://www.f2er.com/2408.html

大家都在问