异步单例模式之不一样的单例模式

本文转载自微信公众号「云的异步样程序世界」,作者云的单例单例世界。转载本文请联系云的模式模式程序世界公众号。

前言

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

异步单例:

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

有人可能会吐槽,模式模式就这,异步样其他方案分分钟搞定。单例单例没错,模式模式没有谁不可被替代。

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

先一起来看一个栗子:

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) } est(); 

到这里,我们的异步单例就可以秀一把,虽然他本意不是干这个,但他可以,因为他满足仅仅一次的条件。

我们看看使用异步单例模式的代码:

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);         }     }; }; 

基础版本

实现代码

注意点:

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

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

2.有人可能会吐槽我,你之前还说过 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 "type");         });         // 置空请求,之后直接用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 "type");         });         // 置空请求,之后直接用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 "type");         });         // 置空请求,之后直接用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[1]肩膀一靠。

// 核心代码 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的编程。

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

async-init[2]

Is it impossible to create a reliable async singleton pattern in JavaScript?[3]

Creating an async singletone in javascript[4]

参考资料

[1]p-is-promise: https://www.npmjs.com/package/p-is-promise

[2]async-init: https://github.com/ert78gb/async-init

[3]Is it impossible to create a reliable async singleton pattern in JavaScript?: https://stackoverflow.com/questions/58919867/is-it-impossible-to-create-a-reliable-async-singleton-pattern-in-javascript

[4]Creating an async singletone in javascript: https://stackoverflow.com/questions/59612076/creating-an-async-singletone-in-javascript

应用开发
上一篇:便宜域名使用如何?小白可以买到便宜域名吗?
下一篇:第六:这个圈子里的域名确实是赚钱的一些大玩家,至于小米农,有多少赚钱?几乎没有,也就是说,轿子里只有一个人,而且大多数人都抬着轿子。