Promise的高阶用法🚀从外部改变promise内部状态

一、前言

Promise 官方文档中表示:Promise 对象的状态不受外界影响。Promise 对象代表一个异步操作,有三种状态:Pending(进行中)、Resolved(已完成,又称 Fulfilled)和 Rejected(已失败)。

只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是 Promise 这个名字的由来,它的英语意思就是「承诺」,表示其他手段无法改变。

但是经过实际测试从外部控制 Promise 的状态其实是可以办到的:Promise 的状态取决于 Promise 主体中是否调用了 resolved 或者 reject,因此我们只要把 resolved 和 reject 赋值给外部变量即可,也就是在外部声明两个变量保存 resolved 和 reject 就行了。

下面通过两个常见的场景来深入的理解:从外部改变 promise 内部状态

二、场景一

1. 需求分析

假设你有多个页面的一些功能需要先收集用户的信息才能允许使用,在点击使用某功能前先弹出信息收集的弹框,你会怎么实现呢?

以下是不同水平的前端同学的实现思路:

初级前端:我写一个模态框,然后复制粘贴到其他页面,效率很杠杠的!

中级前端:你这不便于维护,我们要单独封装一下这个组件,在需要的页面引入使用!

高级前端:封什么装什么封!!!写在所有页面都能调用的地方,一个方法调用岂不更好?

看看高级前端怎么实现的,以Vue3为例来看看下面的示例:

2. 解决方案

模态框组件:声明两个变量用于保存 promise 里的 resolve 和 reject 方法,突破作用域限制

js 复制代码
<template>
  <div class="modal" v-show="visible">
    <div>
      用户姓名:<input v-model="info.name" />
    </div>
    <button @click="handleCancel">取消</button>
    <button @click="handleConfirm">提交</button>
  </div>

</template>

<script setup>
import { provide } from 'vue';

const visible = ref(false);
const info = reactive({
  name: ''
});

let resolveFn, rejectFn;

// 将信息收集函数函数传到下面
provide('getInfoByModal', () => {
  visible.value = true;
  return new Promise((resolve, reject) => {
    // 将两个函数赋值给外部,突破promise作用域
    resolveFn = resolve;
    rejectFn = reject;
  });
})
</script>

点击提交或者取消按钮时修改 Promise 的状态(从外部的形式去修改),比如执行 resolveFn 方法也就相当于 resolve(info),此时 Promise 的状态发生改变,便会执行 .then() 里的回调。

js 复制代码
const handleConfirm = () => {
  resolveFn && resolveFn(info);
};
const handleCancel = () => {
  rejectFn && rejectFn(new Error('用户已取消'));
};

当调用 getInfoByModal 方法时,显示模态框;等待用户点击提交或者取消按钮,当用户点击提交按钮后,Promise 的状态发生了改变,从而执行 .then() 里的回调,将数据上报到后端。

js 复制代码
<template>
  <button @click="handleClick">填写信息</button>
</template>

<script setup>
import { inject } from 'vue';

const getInfoByModal = inject('getInfoByModal');
const handleClick = async () => {
  // 调用后将显示模态框,用户点击确认后会将promise改为fullfilled状态,从而拿到用户信息
  getInfoByModal().then((res) => {
     await api.submitInfo(res);
  })
}
</script>

三、场景二

1. 场景回顾

项目里有一个 Page.vue 页面,项目初始化后加载的顺序是 App.vue -> Page.vue,在 App.vue 加载的时候,请求后端接口获取所需的数据,先来看下 pinia 中的代码:

js 复制代码
import { defineStore } from 'pinia';
import { getUserList, getDepartList } from '/@/api/user.ts'

export const userStore = defineStore({
    id: 'user',
    state: () => ({
        userList: [],
        departList: [],
    }),
    actions: {
        // 获取数据
        async init() {
            this.userList = await getUserList();
            this.departList = await getDepartList();
        }
    }
})

然后在 App.vue 的 onMounted 中去执行 init 方法,接着在 Page.vue 初始化的时候,需要去使用这些数据去完成某些操作。

js 复制代码
// App.vue
import { userStore } from '/@/store/modules/user';
import { onMounted } from 'vue';

const useUserStore = userStore();

onMounted(() => {
    useUserStore.init();
})


// Page.vue
import { userStore } from '/@/store/modules/user';
import { onMounted } from 'vue';

const useUserStore = userStore();

onMounted(() => {
    console.log(useUserStore.userList, useUserStore.departList)
})

正常情况下是这种逻辑是没问题的,但是当接口响应比较慢时,Page.vue 中就有可能拿不到数据,导致后面的逻辑出现 BUG,因此我们需要考虑到这种情况,作出相对应的处理。

2. 解决方案

比较好的解决方法是使用 Promise,而且需要从外部改变 promise 内部状态

先来封装一个函数,这函数返回两个东西

  • readyResolve: 一个 resolve 函数
  • onReady: 接收回调函数,只有在 readyResolve 执行后才会执行

主要逻辑:声明一个 promise,将其 resolve 保存到外部,再声明一个方法,里面执行该 promise 的 .then(),在 then() 里面可以自定义操作。

js 复制代码
export const useOnReady = () => {
    let readyResolve = null;
    const readyPromise = new Promise(resolve => {
        // 保存 resolve
        readyResolve = resolve;
    })
    const onReady = (cb) => {
        readyPromise.then(() => {
            // resolve执行完才会走 then,然后再执行回调函数
            cb();
        })
    }
    
    return {
        onReady,
        readyResolve
    }
}

接着回到 Pinia 文件中,在获取完数据后执行 readyResolve,同时将 onReady 暴露出去:

js 复制代码
import { defineStore } from 'pinia';
import { getUserList, getDepartList } from '/@/api/user.ts'
import { useOnReady } from '/@/hooks/useOnReady'

const { readyResolve, onReady } = useOnReady();

export const userStore = defineStore({
    id: 'user',
    state: () => ({
        userList: [],
        departList: [],
    }),
    actions: {
        // 获取数据
        async init() {
            this.userList = await getUserList();
            this.departList = await getDepartList();
            // 请求完数据,执行readyResolve
            readyResolve();
        }
    }
})

// 将onReady暴露出去
export const onUserStoreSetup = onReady;

在 Page.vue 页面中,只需要往 onUserStoreSetup 中传回调函数即可;当获取完数据,执行 readyResolve,改变了 readyPromise 的状态,接着会执行 .then 中的回调函数,最终成功拿到数据!

js 复制代码
// Page.vue
import { userStore, onUserStoreSetup } from '/@/store/modules/user';
import { onMounted } from 'vue';

const useUserStore = userStore();

onMounted(() => {
    // 传入回调
    onUserStoreSetup(() => {
      console.log(useUserStore.userList, useUserStore.departList)  
    })
})

四、参考文章

晚上12点,帮组员解决Vue3异步引发的线上BUG

整会promise这8个高级用法,再被问倒来喷我

相关推荐
黄尚圈圈18 分钟前
Vue 中引入 ECharts 的详细步骤与示例
前端·vue.js·echarts
浮华似水1 小时前
简洁之道 - React Hook Form
前端
正小安3 小时前
如何在微信小程序中实现分包加载和预下载
前端·微信小程序·小程序
_.Switch5 小时前
Python Web 应用中的 API 网关集成与优化
开发语言·前端·后端·python·架构·log4j
一路向前的月光5 小时前
Vue2中的监听和计算属性的区别
前端·javascript·vue.js
长路 ㅤ   5 小时前
vite学习教程06、vite.config.js配置
前端·vite配置·端口设置·本地开发
长路 ㅤ   5 小时前
vue-live2d看板娘集成方案设计使用教程
前端·javascript·vue.js·live2d
Fan_web5 小时前
jQuery——事件委托
开发语言·前端·javascript·css·jquery
安冬的码畜日常5 小时前
【CSS in Depth 2 精译_044】第七章 响应式设计概述
前端·css·css3·html5·响应式设计·响应式
莹雨潇潇6 小时前
Docker 快速入门(Ubuntu版)
java·前端·docker·容器