一、什么是Promise
Promise是抽象异步处理对象以及对其进行各种操作的组件。Promise本身是同步的立即执行函数解决异步回调的问题, 当调用 resolve 或 reject 回调函数进行处理的时候, 是异步操作, 会先执行.then/catch等,当主栈完成后,才会去调用执行resolve/reject中存放的方法。promise的功能是可以将复杂的异步处理轻松地进行模式化。采用Promise,连续读取多个文件,可以解决回调地狱。
如果说到基于JavaScript的异步处理,我想大多数都会想到利用回调函数。
1.1 resolve
返回一个promise对象成功的状态,并通知给then方法或catch方法,接下来根据对象的状态决定走哪条路,然后执行后续操作;resolve方法的作用是把promise对象的状态从进行中变成已完成,同时可以向resolve方法传入参数,这个参数会在将来被promise对象的then方法获取;1.2 reject
返回一个失败的promise对象;reject方法也是同样的道理,只不过是把promise对象状态变成失败,同时传入的参数会被catch方法获取而已;当resolve或reject修改promise对象状态之后,通过检测promise对象的状态,决定执行then还是catch回调方法。在这个过程中:resolve和reject起到的作用是修改对象状态、通知回调函数以及传递回调函数可能需要的参数。这样做的好处就是:把逻辑判断和回调函数分开处理;
2、回调函数 .then 和 .catch
回调函数的调用是根据对象的状态来完成的:进行中、已完成和失败(pending(进行中)、fullfilled(成功)、rejected(失败)2.1 Promise.then
1. 得到异步任务的正确结果;2. 只有Promsie内部的状态落定了,then方法中对应的处理程序才会执行;
3. then方法的强大之处,可以链式调用;
2.2 Promise.catch
进行捕获和处理失败结果;3、方法 .all() 和 .race()
3.1 Promise.all()
并发处理多个异步任务(等待机制),所有结果都返回之后,统一打印;
谁跑得慢,以谁为准执行回调;
Promise.all 接收一个 promise对象的数组作为参数,当这个数组里的所有promise对象全部变为resolve或reject状态的时候,它才会去调用 .then 方法;
<template>
<div>
<!-- 当数据未加载完成时显示加载动画 -->
<div v-if="!dataLoaded" class="loader">加载中...</div>
<!-- 当数据加载完成后显示数据内容 -->
<div v-else>
<h1>数据加载完成</h1>
<p>数据: {{ data }}</p>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
// 定义一个响应式变量来跟踪数据是否加载完成
const dataLoaded = ref(false);
// 定义一个响应式变量来存储异步获取的数据
const data = ref(null);
// 模拟异步API请求的函数
function fetchData() {
return new Promise((resolve) => {
// 打印日志,表示开始模拟异步API请求
console.log('333 - 开始模拟异步API请求...'); //第二个函数执行
// 使用setTimeout模拟2秒的异步操作
setTimeout(() => { //因为设置了定时器 所以延迟执行打印
// 所以 onMounted -》loadData -》 fetchData -》 按顺序第一遍执行 到定时器已经执行完毕
// 现在回到 onMounted 从新执行
// 打印日志,表示异步API请求已完成
console.log('555 - 异步API请求完成,返回数据...'); //onMounted 已经全都执行完毕 现在按顺序执行打印数据
// 解析Promise,返回模拟的数据
resolve('这是从异步API获取的数据');
}, 2000);
});
}
// 异步加载数据的函数
async function loadData() {
try {
// 打印日志,表示开始加载数据
console.log('222 - 开始加载数据...'); //函数的第一个打印
// 等待异步API请求完成,并获取结果
const result = await fetchData(); //函数里调用的另一个函数
// 将获取的数据存储在响应式变量中
data.value = result;
// 设置数据加载完成的标志
dataLoaded.value = true;
// 打印日志,表示数据已加载完成并存储
console.log('666 - 数据加载完成,并已存储在响应式变量中。'); //onMounted 已经全都执行完毕 现在按顺序执行打印数据
} catch (error) {
// 如果发生错误,打印错误信息
console.error('数据加载失败:', error);
}
}
// 组件挂载时的生命周期钩子
onMounted(() => {
// 打印日志,表示组件挂载开始,准备加载数据
console.log('111 - 组件挂载开始,准备加载数据...'); //初始化 执行的第一个打印条件
// 调用loadData函数开始异步加载数据
loadData(); //初始化 执行的第一个函数 执行函数里边的逻辑 和 打印
// 注意:这里的日志会在loadData内部的异步操作之前打印
// 打印日志,表示组件挂载完成,但数据加载是异步的
console.log('444 - 组件挂载完成,loadData函数已调用(但数据加载是异步的)。'); //第一轮输出到定时器已经结束 现在继续打印onMounted的值
// 实际的异步数据加载会在之后的某个时间点完成,并更新DOM
});
</script>
<style scoped>
.loader {
font-size: 1.5em;
color: gray;
/* 可以添加CSS动画来增强加载效果 */
}
h1 {
color: blue;
}
p {
font-size: 1.2em;
}
</style>
<template>
<div>
<!-- 当数据未加载完成时显示加载动画 -->
<div v-if="!dataLoaded" class="loader">加载中...</div>
<!-- 当数据加载完成后显示数据内容和重新加载按钮 -->
<div v-else>
<h1>数据加载完成</h1>
<p>数据: {{ data }}</p>
<button @click="reloadData">重新加载数据</button>
</div>
<!-- 添加一个按钮来手动触发数据加载(初始时隐藏) -->
<button v-if="!dataLoaded && canManualLoad" @click="loadData">立即加载数据</button>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
// 定义一个响应式变量来跟踪数据是否加载完成
const dataLoaded = ref(false);
// 定义一个响应式变量来存储异步获取的数据
const data = ref(null);
// 定义一个响应式变量来控制是否可以手动加载数据(初始为true,用于演示)
const canManualLoad = ref(true);
// 模拟异步API请求的函数
function fetchData() {
return new Promise((resolve) => {
// 打印日志,表示开始模拟异步API请求
console.log('333 - 开始模拟异步API请求...');
// 使用setTimeout模拟2秒的异步操作
setTimeout(() => {
// 打印日志,表示异步API请求已完成
console.log('444 - 异步API请求完成,返回数据...');
// 解析Promise,返回模拟的数据
resolve('这是从异步API获取的数据');
}, 2000);
});
}
// 异步加载数据的函数
async function loadData() {
if (!canManualLoad.value) {
console.log('手动加载按钮已被禁用,无法加载数据。');
return;
}
try {
// 打印日志,表示开始加载数据
console.log('222 - 开始加载数据...');
// 禁用手动加载按钮,防止重复点击
canManualLoad.value = false;
// 等待异步API请求完成,并获取结果
const result = await fetchData();
// 将获取的数据存储在响应式变量中
data.value = result;
// 设置数据加载完成的标志
dataLoaded.value = true;
// 重新启用手动加载按钮(在实际应用中可能不需要这一步,这里只是为了演示)
// 注意:在实际应用中,如果只需要在组件挂载时加载一次数据,则不需要重新启用按钮
setTimeout(() => {
canManualLoad.value = true;
}, 0); // 使用setTimeout来模拟下一个事件循环,以便在数据加载后立即重新启用按钮(仅用于演示)
// 打印日志,表示数据已加载完成并存储
console.log('555 - 数据加载完成,并已存储在响应式变量中。');
} catch (error) {
// 如果发生错误,打印错误信息,并重新启用手动加载按钮
console.error('数据加载失败:', error);
canManualLoad.value = true; // 重新启用按钮以便用户可以尝试再次加载
}
}
// 组件挂载时的生命周期钩子
onMounted(() => {
// 打印日志,表示组件挂载开始,但此时不自动加载数据(因为增加了手动加载的功能)
// 如果需要自动加载,可以在这里直接调用loadData()
console.log('111 - 组件挂载完成,但未自动加载数据。可以点击“立即加载数据”按钮来加载。');
});
// 定义一个函数来处理重新加载数据的操作
function reloadData() {
// 在重新加载之前,重置数据加载状态和数据
dataLoaded.value = false;
data.value = null;
// 重新调用loadData函数来加载数据
loadData();
}
</script>
<style scoped>
.loader {
font-size: 1.5em;
color: gray;
/* 可以添加CSS动画来增强加载效果 */
}
h1 {
color: blue;
}
p {
font-size: 1.2em;
}
button {
margin-top: 10px;
padding: 5px 10px;
font-size: 1em;
}
</style>