• 如何利用 promise 影响代码的执行顺序?


    Image

    如何利用 promise 影响代码的执行顺序?

    我们写代码的时候,经常会遇到这样的场景。2个不同组件,它们的生命周期本身都是相互独立,毫无关联的,但是它们调用的接口,存在相互依赖的情况。

    我举个例子:

    开发小程序时候,里面 App 有一个 onLaunchhook,在小程序初始化时调用,而 Page 里也有一个 onLoadhook,在页面加载时被调用。正常的执行顺序为:

    // 应用启动
    onLaunch(options)
    // do sth....
    // 页面加载
    onLoad(query)
    
    • 1
    • 2
    • 3
    • 4
    • 5

    但是,我们往往也经常遇到这种 case:

    async onLaunch(){
      store.dispatch('set-token',await getToken())
    }
    async onLoad(){
      // getUserInfo 依赖 token
      setUserInfo(await getUserInfo())
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    现在问题来了,依据上面的执行顺序,getTokengetUserInfo 请求实际上是并发执行的。而我们的预期是,先执行 getToken 并设置好全局 token 值之后,才调用 getUserInfo,这样后端才能依据请求携带的,用户 token 信息,来给我们返回指定的数据,不然那就只有一个 401 了。

    那么我们如何让它们之间产生调用的依赖关系呢? 实际上很简单 promiseevent-emitter 都是方法之一。接下来我们来构建一个最小化模型。

    最小化模型

    我们想要 onLoad一部分的代码的执行在 onLaunch特定代码之后。即把一部分并行跑的代码,变更为串行的顺序,同时也允许原先并行运行的方式。

    根据描述,我们天然的就想到了 Microtask,它运行在每个事件循环的执行代码,和运行Task后,Rerender 前。

    接下来为了实现期望,我们就需要在 onLaunch 中去产生 Promise,然后到 onLoad 中依据 Promise 状态的变化,执行代码。

    那么我们就很容易在一个文件中,构建出一个最小化模型,见下方代码:

    let promise
    
    function producer () {
      console.log('producer start!')
      promise = new Promise((resolve) => {
        setTimeout(() => {
          console.log('promise resolved')
          resolve(Math.random())
        }, 2_000)
      })
      console.log('producer end!')
    }
    
    async function consumer () {
      console.log('consumer start!')
      console.log(await promise)
      console.log('consumer end!')
    }
    
    producer()
    consumer()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    这段代码中,我在 producer 创建了一个 promise,在 2sresolve 一个随机数,然后再在 consumer 中,去 await 它的状态,变为 fulfilled 后打印 consumer end!
    当然 async/await 只是语法糖,你用 then/catch 也是可以的,不过使用 await 有一个好处就是,它在面对非 Promise 对象的时候,它会自动把值进行包裹转化成 Promise,即 Promise.resolve(value)

    接着,让我们把这个模型进行扩充,变为多文件模型。

    // ref.js 创建一个引用
    export default {
      promise: undefined
    }
    
    • 1
    • 2
    • 3
    • 4
    // producer.js
    import ref from './ref.js'
    
    export default () => {
      console.log('producer start!')
      ref.promise = new Promise((resolve) => {
        setTimeout(() => {
          console.log('promise resolved')
          resolve(Math.random())
        }, 2_000)
      })
      console.log('producer end!')
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    // consumer.js
    import ref from './ref.js'
    
    export default async () => {
      console.log('consumer start!')
      console.log(await ref.promise)
      console.log('consumer end!')
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    // index.js
    import producer from './producer.js'
    import consumer from './consumer.js'
    
    producer()
    consumer()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    执行结果同理。

    移花接木

    根据上述的代码,我们就可以对小程序的开发,进行一系列劫持的操作。我们以 uni-app vue2/3 和原生为例。

    // vue2
    Vue.mixin({
      created () {
        if (Array.isArray(this.$options.onLoad) && this.$options.onLoad.length) {
          this.$options.onLoad = this.$options.onLoad.map(fn => {
            return async (params:Record<string, any>) => {
              await ref.promise
              fn.call(this, params)
            }
          })
        }
      }
    })
    
    // vue3
    const app = createSSRApp(App)
    app.mixin({
      created () {
        if (this.$scope) {
          const originalOnLoad = this.$scope.onLoad
          this.$scope.onLoad = async (params:Record<string, any>) => {
            await ref.promise
            originalOnLoad.call(this, params)
          }
        }
      }
    })
    
    // native
    const nativePage = Page
    Page = function (options: Parameters<typeof Page>[0]) {
      if (options.onLoad && typeof options.onLoad === 'function') {
        const originalOnLoad = options.onLoad
        options.onLoad = async function (params: Record<string, any>) {
          await ref.promise
          originalOnLoad.call(this, params)
        }
      }
      nativePage(options)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    思路其实都差不多。

    增强

    上述的方法,虽然达到了目的,但是实在太简陋了,扩展性也很差。

    我们以 ref.js 为例,里面只放了一个 promise 太浪费了,为什么不把它放入全局状态里去呢?这样随时可以取出来进行观察。

    为什么不创建多个 Promise queue 呢? 这样还能循环往复地利用不同的队列,来作为代码执行的信道,同时又能够自定义并发度,超时,执行事件间隔等等。p-queue 就是不错的选择。

    当然,这些也只是抛砖引玉,这些相信大家各自有各自的看法,反正先做到满足当前的需求,再根据进阶的需求进行适当的改造,做出来的才是最适合自己的。

  • 相关阅读:
    软考高级系统架构设计师系列论文十八:论软件三层结构的设计
    XSS-labs靶场实战(七)——第16-18关
    什么是pmp考试?
    C结构体内定义结构体,不能直接赋值。
    python学习笔记——字典
    Petalinux 2020.2 离线编译
    postgresql安装配置和基本操作
    MobaXtem通过SSH远程登录ubuntu系统
    vue axios封装并发请求
    python正则表达式
  • 原文地址:https://blog.csdn.net/qq_33020601/article/details/126943709