单例模式大家都知道,异步单例又为何物。
异步单例:
创建实例需要一定的时间,创建期间,交出执行权,创建完毕后,拿回执行权,返回结果。
有人可能会吐槽,就这,其他方案分分钟搞定。 没错,没有谁不可被替代。
这里主要表达的是一种编程思想,其能改变代码风格, 特定情况下漂亮的解决问题。 多一种手段,多一种选择。
先一起来看一个栗子:asyncInsCreator
延时2秒创建一个对象;getAsyncIns
封装异步对象获取过程;
我们多次调用 getAsyncIns
, 得到同一个对象。
async function asyncInsCreator() {
await delay(2000).run();
return new Object();
}
function getAsyncIns() {
return factory(asyncInsCreator);
}
; (async function test() {
try {
const [ins1, ins2, ins3] = await Promise.all([
getAsyncIns(),
getAsyncIns(),
getAsyncIns()
]);
console.log("ins1:", ins1); // ins1: {}
console.log("ins1===ins2", ins1 === ins2); // ins1===ins2 true
console.log("ins2===ins3", ins2 === ins3); // ins2===ins3 true
console.log("ins3=== ins1", ins3 === ins1); // ins3=== ins1 true
} catch (err) {
console.log("err", err);
}
})();
复制代码
适用场景
异步单例
比如初始化socket.io客户端, indexedDB等等
仅仅一次的情况
举一个例子,我们可以注册多个 load
事件
window.addEventListener("load", function () {
// other code
console.log("load 1");
});
window.addEventListener("load", function () {
// other code
console.log("load 2");
});
复制代码
这要是换做React或者Vue,你先得订阅还得取消订阅,显得麻烦,当然你可以利用订阅发布思想再包装一层:
如果换成如下,是不是赏心悦目:
await loaded();
// TODO::
复制代码
你肯定说,这个我会:
function loaded() {
return new Promise((resove, reject) => {
window.addEventListener("load", resove)
});
}
复制代码
我给你一段测试代码:
下面只会输出 loaded 1
,不会输出loaded 2
。
至于原因:load事件只会触发一次。
function loaded() {
return new Promise((resolve, reject) => {
window.addEventListener("load", ()=> resolve(null));
});
}
async function test() {
await loaded();
console.log("loaded 1");
setTimeout(async () => {
await loaded();
console.log("loaded 2");
}, 1000)
}
test();
复制代码
到这里,我们的异步单例就可以秀一把,虽然他本意不是干这个,但他可以,因为他满足仅仅一次的条件。
我们看看使用异步单例模式的代码:loaded 1
与 loaded 2
都如期到来。
const factory = asyncFactory();
function asyncInsCreator() {
return new Promise((resove, reject) => {
window.addEventListener("load", )
});
}
function loaded() {
return factory(asyncInsCreator)
}
async function test() {
await loaded();
console.log("loaded 1"); // loaded 1
setTimeout(async () => {
await loaded();
console.log("loaded 2"); // loaded 2
}, 1000)
}
test();
复制代码
实现思路
状态
实例创建,其实也就只有简简单单的两种状态:
- 创建中
- 创建完毕
难点在于,创建中的时候,又有新的请求来获取实例。
那么我们就需要一个队列或者数组来维护这些请求队列,等待实例创建完毕,再通知请求方。
如果实例化已经完毕,那么之后就直接返回实例就好了。
变量
我们这里就需要三个变量:
- instance
存储已经创建完毕的实例
- initializing
是否创建中
- requests
来保存哪些处于创建中,发过来的请求
工具方法
delay:
延时一定时间调用指定的函数。
用于后面的超时,和模拟延时。
export function delay(delay: number = 5000, fn = () => { }, context = null) {
let ticket = null;
return {
run(...args: any[]) {
return new Promise((resolve, reject) => {
ticket = setTimeout(async () => {
try {
const res = await fn.apply(context, args);
resolve(res);
} catch (err) {
reject(err);
}
}, delay);
});
},
cancel: () => {
clearTimeout(ticket);
}
};
};
复制代码
基础版本
实现代码
注意点:
instance !== undefined
这个作为判断是否实例化,也就是说可以是null, 仅仅一次的场景下使用,最适合不过了。
这里也是一个局限,如果就是返回undefined
呢, 我保持沉默。
有人可能会吐槽我,你之前还说过 undefined
不可靠,我微微一笑,你觉得迷人吗?
- 失败之后
initializing = false
这个意图,就是某次初始化失败时,会通知之前的全部请求,已失败。
之后的请求,还会尝试初始化。
import { delay } from "../util";
function asyncFactory() {
let requests = [];
let instance;
let initializing = false;
return function initiator(fn: (...args: any) => Promise<any>) {
// 实例已经实例化过了
if (instance !== undefined){
return Promise.resolve(instance);
}
// 初始化中
if (initializing) {
return new Promise((resolve, reject) => {
// 保存请求
requests.push({
resolve,
reject
});
})
}
initializing = true;
return new Promise((resolve, reject) => {
// 保存请求
requests.push({
resolve,
reject
});
fn()
.then(result => {
instance = result;
initializing = false;
processRequests('resolve', instance);
})
.catch(error => {
initializing = false;
processRequests('reject', error);
});
});
}
function processRequests(type: "resolve" | "reject", value: any) {
// 挨个resolve
requests.forEach(q => {
q[type](value);
});
// 置空请求,之后直接用instance
requests = [];
}
}
复制代码
测试代码
const factory = asyncFactory();
async function asyncInsCreator() {
await delay(2000).run();
return new Object();
}
function getAsyncIns() {
return factory(asyncInsCreator);
}
; (async function test() {
try {
const [ins1, ins2, ins3] = await Promise.all([
getAsyncIns(),
getAsyncIns(),
getAsyncIns()
]);
console.log("ins1:", ins1); // ins1: {}
console.log("ins1===ins2", ins1 === ins2); // ins1===ins2 true
console.log("ins2===ins3", ins2 === ins3); // ins2===ins3 true
console.log("ins3=== ins1", ins3 === ins1); // ins3=== ins1 true
} catch (err) {
console.log("err", err);
}
})();
复制代码
存在的问题:
没法传参啊,没法设置this
的上下文啊。
传递参数版本
实现思路:
- 增加参数
context
以及args
参数 Function.prototype.appy
实现代码
import { delay } from "../util";
interface AVFunction<T = unknown> {
(value: T): void
}
function asyncFactory<R = unknown, RR = unknown>() {
let requests: { reject: AVFunction<RR>, resolve: AVFunction<R> }[] = [];
let instance: R;
let initializing = false;
return function initiator(fn: (...args: any) => Promise<R>,
context: unknown, ...args: unknown[]): Promise<R> {
// 实例已经实例化过了
if (instance !== undefined){
return Promise.resolve(instance);
}
// 初始化中
if (initializing) {
return new Promise((resolve, reject) => {
requests.push({
resolve,
reject
})
})
}
initializing = true
return new Promise((resolve, reject) => {
requests.push({
resolve,
reject
})
fn.apply(context, args)
.then(res => {
instance = res;
initializing = false;
processRequests('resolve', instance);
})
.catch(error => {
initializing = false;
processRequests('reject', error);
})
})
}
function processRequests(type: "resolve" | "reject", value: any) {
// 挨个resolve
requests.forEach(q => {
q[type](value);
});
// 置空请求,之后直接用instance
requests = [];
}
}
复制代码
测试代码
interface RES {
p1: number
}
const factory = asyncFactory<RES>();
async function asyncInsCreator(opitons: unknown = {}) {
await delay(2000).run();
console.log("context.name", this.name);
const result = new Object(opitons) as RES;
return result;
}
function getAsyncIns(context: unknown, options: unknown = {}) {
return factory(asyncInsCreator, context, options);
}
; (async function test() {
try {
const context = {
name: "context"
};
const [ins1, ins2, ins3] = await Promise.all([
getAsyncIns(context, { p1: 1 }),
getAsyncIns(context, { p1: 2 }),
getAsyncIns(context, { p1: 3 })
]);
console.log("ins1:", ins1, ins1.p1);
console.log("ins1=== ins2", ins1 === ins2);
console.log("ins2=== ins3", ins2 === ins3);
console.log("ins3=== ins1", ins3 === ins1);
} catch (err) {
console.log("err", err);
}
})();
复制代码
存在的问题
看似完美,要是超时了,怎么办呢?
想到这个问题的人,品论区发文,我给你们点赞。
超时版本
这里就需要借用我们的工具方法delay
:
- 如果超时没有成功,通知所有请求失败。
- 反之,通知所有请求成功。
实现代码
import { delay } from "../util";
interface AVFunction<T = unknown> {
(value: T): void
}
function asyncFactory<R = unknown, RR = unknown>(timeout: number = 5 * 1000) {
let requests: { reject: AVFunction<RR>, resolve: AVFunction<R> }[] = [];
let instance: R;
let initializing = false;
return function initiator(fn: (...args: any) => Promise<R>, context: unknown, ...args: unknown[]): Promise<R> {
// 实例已经实例化过了
if (instance !== undefined){
return Promise.resolve(instance);
}
// 初始化中
if (initializing) {
return new Promise((resolve, reject) => {
requests.push({
resolve,
reject
})
})
}
initializing = true
return new Promise((resolve, reject) => {
requests.push({
resolve,
reject
})
const { run, cancel } = delay(timeout);
run().then(() => {
const error = new Error("操作超时");
processRequests("reject", error);
});
fn.apply(context, args)
.then(res => {
// 初始化成功
cancel();
instance = res;
initializing = false;
processRequests('resolve', instance);
})
.catch(error => {
// 初始化失败
cancel();
initializing = false;
processRequests('reject', error);
})
})
}
function processRequests(type: "resolve" | "reject", value: any) {
// 挨个resolve
requests.forEach(q => {
q[type](value);
});
// 置空请求,之后直接用instance
requests = [];
}
}
interface RES {
p1: number
}
const factory = asyncFactory<RES>();
async function asyncInsCreator(opitons: unknown = {}) {
await delay(1000).run();
console.log("context.name", this.name);
const result = new Object(opitons) as RES;
return result;
}
function getAsyncIns(context: unknown, options: unknown = {}) {
return factory(asyncInsCreator, context, options);
}
; (async function test() {
try {
const context = {
name: "context"
};
const [instance1, instance2, instance3] = await Promise.all([
getAsyncIns(context, { p1: 1 }),
getAsyncIns(context, { p1: 2 }),
getAsyncIns(context, { p1: 3 })
]);
console.log("instance1:", instance1, instance1.p1);
console.log("instance1=== instance2", instance1 === instance2);
console.log("instance2=== instance3", instance2 === instance3);
console.log("instance3=== instance1", instance3 === instance1);
} catch (err) {
console.log("err", err);
}
})();
复制代码
测试代码
当把asyncInsCreator
的 delay(1000)
修改为 delay(6000)
的时候,创建所以的事件6000ms大于 asyncFactory
默认的5000ms,就会抛出下面的异常。
err Error: 操作超时
at c:\projects-github\juejinBlogs\异步单例\queue\args_timeout.ts:40:31
复制代码
interface RES {
p1: number
}
const factory = asyncFactory<RES>();
async function asyncInsCreator(opitons: unknown = {}) {
await delay(1000).run();
console.log("context.name", this.name);
const result = new Object(opitons) as RES;
return result;
}
function getAsyncIns(context: unknown, options: unknown = {}) {
return factory(asyncInsCreator, context, options);
}
; (async function test() {
try {
const context = {
name: "context"
};
const [ins1, ins2, ins3] = await Promise.all([
getAsyncIns(context, { p1: 1 }),
getAsyncIns(context, { p1: 2 }),
getAsyncIns(context, { p1: 3 })
]);
console.log("ins1:", ins1, ins1.p1);
console.log("ins1=== ins2", ins1 === ins2);
console.log("ins2=== ins3", ins2 === ins3);
console.log("ins3=== ins1", ins3 === ins1);
} catch (err) {
console.log("err", err);
}
})();
复制代码
存在的问题
存在的问题:
- 抛出了的Error
new Error("操作超时")
我们简单粗暴的抛出了这个异常,当外围的try/catch
捕获后,还没法区别这个错误的来源。 我们可以再封住一个AsyncFactoryError
,或者 asyncInsCreator
抛出特定一定,交给try/catch
自身去识别。
- 没有判断参数
fn
如果不是一个有效的函数,fn执行后是不是一个返回Promise
。
是不是一个有效的函数好判断。
执行后是不是返回一个Promise
, 借巨人p-is-promise肩膀一靠。
// 核心代码
function isPromise(value) {
return value instanceof Promise ||
(
isObject(value) &&
typeof value.then === 'function' &&
typeof value.catch === 'function'
);
}
复制代码
存在问题,你就不解决了吗? 不解决,等你来动手。
基于订阅发布模式的版本
这里是实现的另外一种思路, 利用订阅发布者。
要点
通过在Promise监听EventEmitter事件, 这里因为只需要监听一次,once
闪亮登场。
new Promise((resolve, reject) => {
emitter.once("initialized", () => {
resolve(instance);
});
emitter.once("error", (error) => {
reject(error);
});
});
复制代码
实现代码
这里就实现一个最基础版本,至于带上下文,参数,超时的版本,大家可以尝试自己实现。
import { EventEmitter } from "events";
import { delay } from "./util";
function asyncFactory<R = any>() {
let emitter = new EventEmitter();
let instance: any = null;
let initializing = false;
return function getAsyncInstance(factory: () => Promise<R>): Promise<R> {
// 已初始化完毕
if (instance !== undefined){
return Promise.resolve(instance);
}
// 初始化中
if (initializing === true) {
return new Promise((resolve, reject) => {
emitter.once("initialized", () => {
resolve(instance);
});
emitter.once("error", (error) => {
reject(error);
});
});
}
initializing = true;
return new Promise((resolve, reject) => {
emitter.once("initialized", () => {
resolve(instance);
});
emitter.once("error", (error) => {
reject(error);
});
factory()
.then(ins => {
instance = ins;
initializing = false;
emitter.emit("initialized");
emitter = null;
})
.catch((error) => {
initializing = false;
emitter.emit("error", error);
});
})
}
}