问题描述
-
先要了解四个基本概念:
-
同步(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是异步的 ,后续对
tagname
和tagcontent
的赋值以及对newDiv1
和newDiv2
的内容修改看上去是在fetch
请求之后立即执行的,这可能导致在fetch
请求发送后(此时fetch还未执行完,即还没有得到结果),JavaScript会继续执行后续的代码,而不会等待fetch
请求返回数据。因此,在newDiv1.textContent=tagname;
和newDiv2.textContent=tagcontent;
这两行代码执行时,tagname
和tagcontent
可能尚未被赋值,它们将是undefined
。这可能导致newDiv1.textContent
和newDiv2.textContent
的内容也变为undefined
,而不是预期的标签名字和标签内容。
回调函数
- 为了解决这个问题,我们可以通过这些异步函数的回调来处理可以将对
newDiv1.textContent
和newDiv2.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()执行完成辣