觉醒吧,异步单例模式 - 不一样的单例模式

简介: 异步单例:创建实例需要一定的时间,创建期间,交出执行权,创建完毕后,拿回执行权,返回结果。

1.JPG


前言


单例模式大家都知道,异步单例又为何物。


异步单例


创建实例需要一定的时间,创建期间,交出执行权,创建完毕后,拿回执行权,返回结果。


有人可能会吐槽,就这,其他方案分分钟搞定。 没错,没有谁不可被替代。


这里主要表达的是一种编程思想,其能改变代码风格, 特定情况下漂亮的解决问题。

多一种手段,多一种选择。


先一起来看一个栗子:


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 1loaded 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();
复制代码


实现思路


状态


实例创建,其实也就只有简简单单的两种状态:


  1. 创建中
  2. 创建完毕


难点在于,创建中的时候,又有新的请求来获取实例。


那么我们就需要一个队列或者数组来维护这些请求队列,等待实例创建完毕,再通知请求方。


如果实例化已经完毕,那么之后就直接返回实例就好了。


变量


我们这里就需要三个变量:

  1. instance

存储已经创建完毕的实例

  1. initializing

是否创建中

  1. 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);
        }
    };
};
复制代码


基础版本


实现代码


注意点:


  1. instance !== undefined

这个作为判断是否实例化,也就是说可以是null, 仅仅一次的场景下使用,最适合不过了。


这里也是一个局限,如果就是返回undefined呢, 我保持沉默。


有人可能会吐槽我,你之前还说过 undefined不可靠,我微微一笑,你觉得迷人吗?


  1. 失败之后 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的上下文啊。


传递参数版本



实现思路:


  1. 增加参数 context 以及 args参数
  2. 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);
    }
})();
复制代码


测试代码


当把asyncInsCreatordelay(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);
    }
})();
复制代码


存在的问题


存在的问题:


  1. 抛出了的Error new Error("操作超时")

我们简单粗暴的抛出了这个异常,当外围的try/catch捕获后,还没法区别这个错误的来源。 我们可以再封住一个AsyncFactoryError,或者 asyncInsCreator 抛出特定一定,交给try/catch 自身去识别。


  1. 没有判断参数 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);
                });
        })
    }
}
复制代码


总结


异步单例不多见,这里要表达的是一种思想,把基于事件的编程,变为基于Promise的编程。


这里其实还涉及一些设计模式, 学以致用,投入实际代码中,解决问题,带来收益,这才是我们追求的。


写在最后


写作不易,您的一赞一评就是我前行的动力。

相关文章
|
2天前
|
缓存 安全 Java
【JavaEE】——单例模式引起的多线程安全问题:“饿汉/懒汉”模式,及解决思路和方法(面试高频)
单例模式下,“饿汉模式”,“懒汉模式”,单例模式下引起的线程安全问题,解锁思路和解决方法
|
2月前
|
设计模式 安全 Java
【多线程-从零开始-柒】单例模式,饿汉和懒汉模式
【多线程-从零开始-柒】单例模式,饿汉和懒汉模式
58 0
|
6月前
|
设计模式 SQL 安全
Java设计模式:单例模式之六种实现方式详解(二)
Java设计模式:单例模式之六种实现方式详解(二)
|
7月前
|
设计模式 安全 Java
Java多线程基础-8:单例模式及其线程安全问题(二)
单例模式是软件设计模式之一,确保一个类只有一个实例并提供全局访问点。
65 0
|
7月前
|
设计模式 安全 Java
Java多线程基础-8:单例模式及其线程安全问题(一)
本文介绍了设计模式中的单例模式,它是软件开发中的经典模式,确保某个类在程序运行期间只有一个实例。
72 0
|
7月前
|
设计模式 安全 Java
多线程案例-单例模式
多线程案例-单例模式
272 0
|
设计模式 Java 数据库连接
多线程案例(1)-单例模式
多线程案例(1)-单例模式
82 0
|
安全 Java
并发编程-09安全发布对象+单例模式详解
并发编程-09安全发布对象+单例模式详解
80 0
并发编程-09安全发布对象+单例模式详解
|
设计模式 SQL 安全
【设计模式学习笔记】单例模式详解(懒汉式遇上多线程问题解析基于C++实现)
【设计模式学习笔记】单例模式详解(懒汉式遇上多线程问题解析基于C++实现)
360 0
【设计模式学习笔记】单例模式详解(懒汉式遇上多线程问题解析基于C++实现)
|
设计模式 安全 编译器
设计模式之单例模式(懒汉, 饿汉)
设计模式之单例模式(懒汉, 饿汉)
196 0
设计模式之单例模式(懒汉, 饿汉)