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

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

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

我举个例子:

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

// 应用启动
onLaunch(options)
// do sth....
// 页面加载
onLoad(query) 

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

async onLaunch(){store.dispatch('set-token',await getToken())
}
async onLoad(){// getUserInfo 依赖 tokensetUserInfo(await getUserInfo())
} 

现在问题来了,依据上面的执行顺序,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() 

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

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

// ref.js 创建一个引用
export default {promise: undefined
} 
// 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!')
} 
// consumer.js
import ref from './ref.js'

export default async () => {console.log('consumer start!')console.log(await ref.promise)console.log('consumer end!')
} 
// index.js
import producer from './producer.js'
import consumer from './consumer.js'

producer()
consumer() 

执行结果同理。

移花接木

根据上述的代码,我们就可以对小程序的开发,进行一系列劫持的操作。我们以 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.promisefn.call(this, params)}})}}
})

// vue3
const app = createSSRApp(App)
app.mixin({created () {if (this.$scope) {const originalOnLoad = this.$scope.onLoadthis.$scope.onLoad = async (params:Record<string, any>) => {await ref.promiseoriginalOnLoad.call(this, params)}}}
})

// native
const nativePage = Page
Page = function (options: Parameters<typeof Page>[0]) {if (options.onLoad && typeof options.onLoad === 'function') {const originalOnLoad = options.onLoadoptions.onLoad = async function (params: Record<string, any>) {await ref.promiseoriginalOnLoad.call(this, params)}}nativePage(options)
} 

思路其实都差不多。

增强

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

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

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

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

最后

最近找到一个VUE的文档,它将VUE的各个知识点进行了总结,整理成了《Vue 开发必须知道的36个技巧》。内容比较详实,对各个知识点的讲解也十分到位。



有需要的小伙伴,可以点击下方卡片领取,无偿分享

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值