回调地狱-前端发送请求的阻塞问题解决

发布于 2024-01-29  265 次阅读


问题描述

  • 先要了解四个基本概念:

  • 同步(Synchronous): 同步指的是程序按照预定顺序执行,每个操作都要等待前一个操作完成后才能继续执行。在同步模型中,一个操作的完成依赖于另一个操作的开始和结束。

  • 异步(Asynchronous): 异步指的是程序的执行不按照预定顺序,可以在一个操作进行的同时继续执行其他操作,不需要等待前一个操作完成。在异步模型中,一个操作的开始不会阻塞其他操作的执行。

  • 阻塞(Blocking): 阻塞是指一个操作在等待某个事件完成时会暂时停止执行,直到该事件完成后才能继续执行。

  • 非阻塞(Non-blocking): 非阻塞是指一个操作在等待某个事件完成时不会停止执行,而是会继续执行其他操作,定期检查事件是否完成。

  • js是异步的,或者说是非阻塞的,这使得js的执行不是按顺序执行的,通常情况下这不会造成什么问题,但是如果使用了下面的代码就可能出现错误

let tagname, tagcontent;

fetch('https://api.example.com/tagName')
  .then(response => response.json())
  .then(data => {
    // 修改标签的内容为API返回的名字
   tagname = data.tagName;
   tagcontent = data.tagcontent;
  })
  .catch(error => {
    console.error('Error fetching tag name:', error);
    newDiv.textContent = 'Error fetching tag name';
  });

// 创建一个新的div标签
const newDiv1 = document.createElement('div');
newDiv.textContent = 'Loading...'; // 初始文本内容为'Loading...'
document.body.appendChild(newDiv1); // 将新标签添加到页面中

// 再创建一个新的div标签
const newDiv2 = document.createElement('div');
newDiv.textContent = 'Loading...'; // 初始文本内容为'Loading...'
document.body.appendChild(newDiv2); // 将新标签添加到页面中

// 发起API请求获取标签的名字

newDiv1.textContent=tagname;
newDiv2.textContent=tagcontent;
  • 这段js看起来没有任何问题,先定义两个变量,然后向api发送请求拿到了两个变量的值,最后创建两个div并且把值传进去
  • 在这段代码中,存在一个常见的问题,涉及到异步操作的执行顺序和时机。由于js是异步的 ,后续对tagnametagcontent的赋值以及对newDiv1newDiv2的内容修改看上去是在fetch请求之后立即执行的,这可能导致在fetch请求发送后(此时fetch还未执行完,即还没有得到结果),JavaScript会继续执行后续的代码,而不会等待fetch请求返回数据。因此,在newDiv1.textContent=tagname;newDiv2.textContent=tagcontent;这两行代码执行时,tagnametagcontent可能尚未被赋值,它们将是undefined。这可能导致newDiv1.textContentnewDiv2.textContent的内容也变为undefined,而不是预期的标签名字和标签内容。

回调函数

  • 为了解决这个问题,我们可以通过这些异步函数的回调来处理可以将对newDiv1.textContentnewDiv2.textContent的修改放在fetch请求成功后的then方法中。这样可以确保在数据准备好之后再修改标签内容,如下所示:
// 创建一个新的div标签
const newDiv1 = document.createElement('div');
newDiv.textContent = 'Loading...'; // 初始文本内容为'Loading...'
document.body.appendChild(newDiv1); // 将新标签添加到页面中
// 创建一个新的div标签
const newDiv2 = document.createElement('div');
newDiv.textContent = 'Loading...'; // 初始文本内容为'Loading...'
document.body.appendChild(newDiv2); // 将新标签添加到页面中

// 发起API请求获取标签的名字
fetch('https://api.example.com/tagName')
  .then(response => response.json())
  .then(data => {
    // 修改标签的内容为API返回的名字
    newDiv1.textContent = data.tagName;
    newDiv2.textContent = data.tagName;
  })
  .catch(error => {
    console.error('Error fetching tag name:', error);
    newDiv.textContent = 'Error fetching tag name';
  });
  • 通过将修改标签内容的操作放在then方法中,可以确保在API返回数据后再修改标签内容,避免了由于异步操作导致的时序问题。

  • 在JavaScript中,回调函数是作为参数传递给其他函数的函数,以便在某个特定事件发生或操作完成时执行。回调函数允许我们处理异步操作,确保在特定事件发生后执行相应的代码。

  • 对于fetch的回调

fetch('https://api.example.com/tagName')
  .then(response => response.json())
  .then(data => {
    tagname = data.tagName;
    tagcontent = data.tagcontent;

    // 在获取数据后执行的操作
    // 这里可以对数据进行处理或更新页面内容等操作
  })
  .catch(error => {
    console.error('Error fetching tag name:', error);
    // 在请求失败时执行的操作
  });
  • 对于ajax的回调
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
  if (this.readyState == 4 && this.status == 200) {
    // 请求成功时执行的操作
    console.log(this.responseText);
  } else {
    // 请求失败时执行的操作
    console.error('请求失败');
  }
};
xhttp.open('GET', 'https://api.example.com/data', true);
xhttp.send();
  • 对于xhr的回调
var xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.example.com/data', true);
xhr.onload = function() {
  if (xhr.status >= 200 && xhr.status < 300) {
    // 请求成功时执行的操作
    console.log(xhr.responseText);
  } else {
    // 请求失败时执行的操作
    console.error('请求失败');
  }
};
xhr.onerror = function() {
  console.error('请求发生错误');
};
xhr.send();
  • 对于axios的回调
axios.get('https://api.example.com/data')
  .then(response => {
    // 请求成功时执行的操作
    console.log(response.data);
  })
  .catch(error => {
    // 请求失败时执行的操作
    console.error(error);
  });

回调地狱与解决

  • 回调函数可以解决异步和非阻塞的问题,但是遇到多个前后顺序的需求(比如先验证验证码,然后验证用户名,然后验证密码)可能出现这样的函数层层嵌套情况

  • 可以使用promise对象来解决,不过后来有了更好看的await+async,这里直接用后者了

  • 使用 async/await 是另一种处理异步操作的方式,它建立在 Promise 的基础上,使异步代码看起来更像同步代码,从而提高可读性。async/await 是 ECMAScript 2017 的新特性,让异步代码的编写更加简洁和直观。

  • 下面是一个使用 async/await 处理异步操作的示例:

// 模拟一个异步函数获取数据
function fetchData() {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve('这是从服务器获取的数据');
    }, 2000);
  });
}

// 使用 async/await 处理异步操作
async function displayData() {
  try {
    const data = await fetchData();
    console.log(data); // 输出从服务器获取的数据
    document.getElementById('output').innerText = data; // 将数据显示在页面上
  } catch (error) {
    console.error('出现错误:', error);
  }
}

displayData();
  • 上面那个函数相当于一个sleep(2),模拟了一个阻塞的情况,displayData()里面调用了fetchData(),fetchData()延迟两秒,displayData()不会等fetchData(),就直接执行后面的内容了,就会出问题
  • 加上这对关键字之后displayData()就会等fetchData()执行完成辣
届ける言葉を今は育ててる
最后更新于 2024-03-09