最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 觉醒吧,异步单例模式 - 不一样的单例模式

    正文概述 掘金(云的世界)   2021-07-01   459

    前言

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

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

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

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

    先一起来看一个栗子:
    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的编程。
    这里其实还涉及一些设计模式, 学以致用,投入实际代码中,解决问题,带来收益,这才是我们追求的。

    写在最后

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


    起源地下载网 » 觉醒吧,异步单例模式 - 不一样的单例模式

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元