🚀从外部改变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个高级用法,再被问倒来喷我

相关推荐
web1309332039819 分钟前
前端下载后端文件流,文件可以下载,但是打不开,显示“文件已损坏”的问题分析与解决方案
前端
王小王和他的小伙伴21 分钟前
解决 vue3 中 echarts图表在el-dialog中显示问题
javascript·vue.js·echarts
学前端的小朱25 分钟前
处理字体图标、js、html及其他资源
开发语言·javascript·webpack·html·打包工具
outstanding木槿30 分钟前
react+antd的Table组件编辑单元格
前端·javascript·react.js·前端框架
好名字08211 小时前
前端取Content-Disposition中的filename字段与解码(vue)
前端·javascript·vue.js·前端框架
摇光931 小时前
js高阶-async与事件循环
开发语言·javascript·事件循环·宏任务·微任务
隐形喷火龙1 小时前
element ui--下拉根据拼音首字母过滤
前端·vue.js·ui
m0_748241122 小时前
Selenium之Web元素定位
前端·selenium·测试工具
风无雨2 小时前
react杂乱笔记(一)
前端·笔记·react.js
前端小魔女2 小时前
2024-我赚到自媒体第一桶金
前端·rust