你或许在项目中遇到过这样的情况。
- 成员A成员B都用得上一个后端接口api,但它们互相不知道对方什么时候请求这个接口,因此导致一打开页面,同一个接口竟然重复请求了多次。
- 由于用户手抖,又因为成员忘记做请求的loading防误触处理,导致一个接口被用于疯狂请求,最终数据乱套,页面不可用。
- SPA单页面应用,多个页面甚至是多个组件可能有同样的数据请求,完全可以共享的数据却不得不重复请求,影响页面加载效率。
- 想要用节流或者防抖解决上面的问题,但是后端返回数据的时间浮动太大,导致不知道应该设置多长的时间。
这些请求浪费,实际上都有调用异步函数(async function
)的参与的;因此,它们虽不是async function
的问题,但却可以利用async function
的特点来解决。
async function
本质上是一个Promise
。因此只要利用好Promise
的特性,就能解决这些问题。
once-init
正是为解决这些问题而生。它从 Promise
的定义出发,用 Promise
的基础功能彻底地阻止了异步请求浪费的发生。
我用它做了两件事:
- 缓存请求的返回值;
- 缓存Promise请求本身;
原理
once-init
的核心思想是缓存和执行队列;
缓存返回值
实现缓存返回值并不困难,只要写一个单例模式就好了。下面是一个缓存的单例模式的简单示例;
class OnceInit {
cache = undefined;
func;
constructor(asyncFunc) {
this.func = asyncFunc;
}
async init() {
if (typeof this.cache !== 'undefined') {
return this.cache;
} else {
const res = await this.func();
this.cache = res;
return res;
}
}
}
// 使用
const oiFoo = new OnceInit(someAsyncFunc);
await oiFoo.init();
复制代码
- 如果缓存已经有值,返回缓存的值;
- 如果缓存没有值,执行异步函数;执行完毕后,更新缓存;
这是一个简易的解决方案,它大概能解决10%的异步函数相关的问题,因为在第一次执行Promise
完成之后,就不会再进行请求,也就不会产生浪费了;
但是,它没有解决多个Promise
同时发生的情况。
假设开发人员同一时间多次调用init
,如果第一次调用的Promise
还没有完成,cache
也还没有初始化,就会导致同一时间的所有调用依旧创建新的Promise
。
甚至有可能因为多次请求,不断的变化cache
,你甚至没有办法确定最后cache
的值是不是你最后一次请求的返回值。
如果要解决这个问题,就需要利用Promise
的特性,同一时间,同一个async function,只允许同一个Promise处在pending状态。
缓存 Promise
-
如果
Promise
正在执行,就不创建新的Promise
;直接返回正在执行的Promise
的返回值; -
如果没有
Promise
正在执行,就创建并缓存新的Promise
;Promise
执行结束之后,删除缓存的Promise
;
class OnceInit {
cache = undefined;
func;
promise = undefined;
constructor(asyncFunc) {
this.func = asyncFunc;
}
async init() {
if (typeof this.cache !== 'undefined') {
return this.cache;
} else {
if (this.promise) {
return await this.promise;
} else {
const promise = this.func();
promise.finally(() => {
this.promise = undefined;
})
this.promise = promise;
const res = await promise;
this.cache = res;
return res;
}
}
}
}
复制代码
通过这种方式,就能避免promise
同一时间重复执行。这也是once-init
这个库的核心思想。
当然这个简单实现还有很多问题需要解决。
- 如果想要刷新缓存,或者不想用缓存怎么办?(原理见这篇文章-不缓存返回值,只防止 Promise 的重复请求应该怎么做?)
- 如果
asyncFunc
需要参数怎么办; - 怎样提供
Typescript
支持;
不过这些问题 once-init
都已经解决了。如果你读过我的上一篇文章juejin.cn/post/704666…,就知道我在年初就写了一个库封装这个想法。
很多伙伴都对我的实现提出了可靠的建议,为我修正改进项目帮助很大。经过这一段时间的打磨,并在实际生产环境中试用了一段时间后,现在终于推出了它的v1.0.0
正式版本了。