likes
comments
collection
share

一个 bug 引发的 promise 面试题思考 (50% 的人不知道如何调整 promise 解决)

作者站长头像
站长
· 阅读数 78

背景: 最近拿到一个vue2的历史仓库,在开发的时候发现一个诡异的问题。此仓库技术栈 vue2.0、vue-i18n 8.0.0版本。 具体现象是: 当用户点击 登出按钮,需要调用一个接口,在接口的 then 里面清除了 登录态,然后在 业务组件的 then里面执行跳转逻辑(通过 this.$i18n.path 拿到跳转的 path) 执行,结果报错了。 前面为分析问题的过程, 可以直接跳到面试题部分

下面抽象下具体的代码逻辑:

// common-logout.js 此方法是一个公共的方法,属于全局封装
// invokeClearCookie 是一个接口,返回一个 promise
const invokeClearCookie = () => { ... }

// 全局公共方法
Vue.prototype.$logout = (ctx) => {
    let that = ctx || this
    const clear = () => {
        that.$store.commit('SET_CN_REWARDS_ENTER', false)
    }
    
    return invokeClearCookie().then((res) => {
        clear()
        return res
    })
}
// 组件渲染的逻辑
<ComA v-if="islogin"/>
<ComB v-else/>
// ComA 组件 里面的 点击 退出按钮 逻辑 (点击退出 执行 logout方法)
methods: {
        logout() {
            // 退出登录
            this.$logout(this).then((res) => {
                if (+res.code == 0) {
                   this.$router.push(this.$i18n.path('/'))
                }
            })
        },
}

执行逻辑分析:

  1. 当用户点击了 退出登录 按钮,执行了 ComA 组件的 logout 方法
  2. ComA 组件的 logout 方法 调用了 全局的 this.$logout
  3. 全局的 this.$logout 方法 调用了一个接口,返回了一个 promise
  4. invokeClearCookie方法的 接口返回来之后,首先执行的是 全局方法 this.$logout的then (也就是公共方法的then 比 ComA 组件的 then 执行时机要早, 因为本质上 业务组件ComA 的then 是基于 全局方法的 this.$logout then 的后续)
  5. 执行 全局方法 $logout 的then 会触发一个 vuex 的 clear() 逻辑, 这个clear 逻辑就是让 islogin 变为 false (islogin 用在渲染 ComA 组件)
  6. 此时在 执行 ComA 组件 的 then 逻辑 (此时就出现问题了)

问题的原因

  1. 全局方法 this.$logout 的then 执行时机 比 业务组件的 then 执行时机要早
  2. 全局方法 this.$logout 的 then 里面执行了 vuex 的逻辑,本质上就是让 islogin 变为 false
  3. 此时问题来了, 因为 islogin 变为false, 触发了 ComA 组件的卸载, 但是 ComA 组件的 then 还没有执行 (因为此时还在执行 全局方法 this.$logout 的 then,也就是 clear(), clear() 执行 是同步的, clear() 执行完立马 触发了 ComA的卸载,因为 v-if 条件变了)
  4. 但是 此时 业务组件的 ComA 的 then 还没有执行
  5. vue-i18n 在 8.0.0 版本在 beforeDestroy 销毁了 _i18 属性

一个 bug 引发的 promise 面试题思考 (50% 的人不知道如何调整 promise 解决)

  1. 因为 vue-i18n 在 beforeDestroy 销毁了 _i18 属性,但是 此时 ComA 组件的 then 还没有执行,现在还在 执行 公共方法 then 里面的 clear逻辑 (这时候的函数调用栈是 clear -> ComA 卸载 -> vue-i18n 的 beforeDestroy)。
  2. 所以当 ComA 组件的 then 执行时,通过 this.$i18n.path 拼接属性就会报错 (因为 $i18n 已经被销毁了)

如何改动

  1. 组内也稍微讨论了下改动的方案,方案很多,这里不赘述如何改动,最终我们是升级 vue-i18n 版本规避这个问题。
  2. 但是引发了一个 promise 面试题,下面我抽象下,有兴趣的同学试一下

抽象出来的面试题

const invokeClearCookie = () => {
    return new Promise((r) => {
        setTimeout(() => {
             r('我是promise')
        }, 1000)
    })
}

const $logout = (ctx) => {
    const clear = () => {
        console.log('我是 clear')
    }
    
    const p1 = invokeClearCookie()

    return p1.then((res)=>{
        console.log('p1 的 then 里面执行的 clear')
        clear()
    });
}

$logout().then((res) => {
    console.log('out - res1', res)
    return res;
}).then((res2) => {
    console.log('out - res2', res2)
})
  1. 此时的打印结果是 'p1 的 then 里面执行的 clear' -> '我是 clear' -> 'out - res1' -> 'out - res2'
  2. 现在要求 打印结果变成 'out - res1' -> 'out - res2' -> 'p1 的 then 里面执行的 clear' -> '我是 clear', 该 如何调整 $logout 里面的 逻辑呢?
  3. 这样调整的目的: 让业务组件ComA 的 then 先执行,等业务组件的 then 的执行完成,在去执行 公共方法的 then,此时可以规避 _i18 属性被销毁的问题 (因为 公共方法的 then 推迟执行了,所以 销毁逻辑也推迟了)

答案:如何调整 promise 执行顺序 (保证业务组件的 then 先执行)

const invokeClearCookie = () => {
    return new Promise((r) => {
        setTimeout(() => {
             r('我是promise')
        }, 1000)
    })
}

const $logout = (ctx) => {
    const clear = () => {
        console.log('我是 clear')
    }
    
    // ** 调整的关键 **
    const p1 = invokeClearCookie()
    
    // ** 调整的关键 **
    p1.then((res) => {
         // ** 调整的关键 **
        return Promise.resolve(res);
    }).then((res)=>{
        console.log('p1 的 then 里面执行的 clear')
        clear()
    });

    return p1
}

$logout().then((res) => {
    console.log('out - res1', res)
    return res;
}).then((res2) => {
    console.log('out - res2', res2)
})
转载自:https://juejin.cn/post/7360970130913083455
评论
请登录