異步單例模式之不一樣的單例模式
本文轉(zhuǎn)載自微信公眾號(hào)「云的程序世界」,作者云的世界。轉(zhuǎn)載本文請(qǐng)聯(lián)系云的程序世界公眾號(hào)。
前言
單例模式大家都知道,異步單例又為何物。
異步單例:
創(chuàng)建實(shí)例需要一定的時(shí)間,創(chuàng)建期間,交出執(zhí)行權(quán),創(chuàng)建完畢后,拿回執(zhí)行權(quán),返回結(jié)果。
有人可能會(huì)吐槽,就這,其他方案分分鐘搞定。沒錯(cuò),沒有誰不可被替代。
這里主要表達(dá)的是一種編程思想,其能改變代碼風(fēng)格, 特定情況下漂亮的解決問題。多一種手段,多一種選擇。
先一起來看一個(gè)栗子:
asyncInsCreator延時(shí)2秒創(chuàng)建一個(gè)對(duì)象;
getAsyncIns 封裝異步對(duì)象獲取過程;
我們多次調(diào)用 getAsyncIns, 得到同一個(gè)對(duì)象。
- 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等等
僅僅一次的情況
舉一個(gè)例子,我們可以注冊(cè)多個(gè) load事件
- window.addEventListener("load", function () {
 - // other code
 - console.log("load 1");
 - });
 - window.addEventListener("load", function () {
 - // other code
 - console.log("load 2");
 - );
 
這要是換做React或者Vue,你先得訂閱還得取消訂閱,顯得麻煩,當(dāng)然你可以利用訂閱發(fā)布思想再包裝一層:
如果換成如下,是不是賞心悅目:
- await loaded();
 - // TODO::
 
你肯定說,這個(gè)我會(huì):
- function loaded() {
 - return new Promise((resove, reject) => {
 - window.addEventListener("load", resove)
 - });
 - }
 
我給你一段測(cè)試代碼:
下面只會(huì)輸出 loaded 1,不會(huì)輸出loaded 2。
至于原因:load事件只會(huì)觸發(fā)一次。
- 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();
 
到這里,我們的異步單例就可以秀一把,雖然他本意不是干這個(gè),但他可以,因?yàn)樗麧M足僅僅一次的條件。
我們看看使用異步單例模式的代碼:
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();
 
實(shí)現(xiàn)思路
狀態(tài)
實(shí)例創(chuàng)建,其實(shí)也就只有簡簡單單的兩種狀態(tài):
- 創(chuàng)建中
 - 創(chuàng)建完畢
 
難點(diǎn)在于,創(chuàng)建中的時(shí)候,又有新的請(qǐng)求來獲取實(shí)例。
那么我們就需要一個(gè)隊(duì)列或者數(shù)組來維護(hù)這些請(qǐng)求隊(duì)列,等待實(shí)例創(chuàng)建完畢,再通知請(qǐng)求方。
如果實(shí)例化已經(jīng)完畢,那么之后就直接返回實(shí)例就好了。
變量
我們這里就需要三個(gè)變量:
- instance 存儲(chǔ)已經(jīng)創(chuàng)建完畢的實(shí)例
 - initializing 是否創(chuàng)建中
 - requests 來保存哪些處于創(chuàng)建中,發(fā)過來的請(qǐng)求
 
工具方法
delay:
延時(shí)一定時(shí)間調(diào)用指定的函數(shù)。
用于后面的超時(shí),和模擬延時(shí)。
- 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);
 - }
 - };
 - };
 
基礎(chǔ)版本
實(shí)現(xiàn)代碼
注意點(diǎn):
1.instance !== undefined這個(gè)作為判斷是否實(shí)例化,也就是說可以是null, 僅僅一次的場景下使用,最適合不過了。
這里也是一個(gè)局限,如果就是返回undefined呢, 我保持沉默。
2.有人可能會(huì)吐槽我,你之前還說過 undefined不可靠,我微微一笑,你覺得迷人嗎?
失敗之后 initializing = false這個(gè)意圖,就是某次初始化失敗時(shí),會(huì)通知之前的全部請(qǐng)求,已失敗。
之后的請(qǐng)求,還會(huì)嘗試初始化。
- import { delay } from "../util";
 - function asyncFactory() {
 - let requests = [];
 - let instance;
 - let initializing = false;
 - return function initiator(fn: (...args: any) => Promise<any>) {
 - // 實(shí)例已經(jīng)實(shí)例化過了
 - if (instance !== undefined){
 - return Promise.resolve(instance);
 - }
 - // 初始化中
 - if (initializing) {
 - return new Promise((resolve, reject) => {
 - // 保存請(qǐng)求
 - requests.push({
 - resolve,
 - reject
 - });
 - })
 - }
 - initializing = true;
 - return new Promise((resolve, reject) => {
 - // 保存請(qǐng)求
 - 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) {
 - // 挨個(gè)resolve
 - requests.forEach(q => {
 - q[type](value "type");
 - });
 - // 置空請(qǐng)求,之后直接用instance
 - requests = [];
 - }
 - }
 
測(cè)試代碼
- 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);
 - }
 - })();
 
存在的問題:
沒法傳參啊,沒法設(shè)置this的上下文啊。
傳遞參數(shù)版本
實(shí)現(xiàn)思路:
- 增加參數(shù) context 以及 args參數(shù)
 - Function.prototype.appy
 
實(shí)現(xiàn)代碼
- 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> {
 - // 實(shí)例已經(jīng)實(shí)例化過了
 - 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) {
 - // 挨個(gè)resolve
 - requests.forEach(q => {
 - q[type](value "type");
 - });
 - // 置空請(qǐng)求,之后直接用instance
 - requests = [];
 - }
 - }
 
測(cè)試代碼
- 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);
 - }
 - })();
 
存在的問題
看似完美,要是超時(shí)了,怎么辦呢?
想到這個(gè)問題的人,品論區(qū)發(fā)文,我給你們點(diǎn)贊。
超時(shí)版本
這里就需要借用我們的工具方法delay:
- 如果超時(shí)沒有成功,通知所有請(qǐng)求失敗。
 - 反之,通知所有請(qǐng)求成功。
 
實(shí)現(xiàn)代碼
- 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> {
 - // 實(shí)例已經(jīng)實(shí)例化過了
 - 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("操作超時(shí)");
 - 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) {
 - // 挨個(gè)resolve
 - requests.forEach(q => {
 - q[type](value "type");
 - });
 - // 置空請(qǐng)求,之后直接用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);
 - }
 - })();
 
測(cè)試代碼
當(dāng)把a(bǔ)syncInsCreator的 delay(1000)修改為 delay(6000)的時(shí)候,創(chuàng)建所以的事件6000ms大于 asyncFactory默認(rèn)的5000ms,就會(huì)拋出下面的異常。
- err Error: 操作超時(shí)
 - 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("操作超時(shí)")我們簡單粗暴的拋出了這個(gè)異常,當(dāng)外圍的try/catch捕獲后,還沒法區(qū)別這個(gè)錯(cuò)誤的來源。我們可以再封住一個(gè)AsyncFactoryError,或者 asyncInsCreator 拋出特定一定,交給try/catch 自身去識(shí)別。
 - 沒有判斷參數(shù) fn如果不是一個(gè)有效的函數(shù),fn執(zhí)行后是不是一個(gè)返回Promise。
 
是不是一個(gè)有效的函數(shù)好判斷。
執(zhí)行后是不是返回一個(gè)Promise, 借巨人p-is-promise[1]肩膀一靠。
- // 核心代碼
 - function isPromise(value) {
 - return value instanceof Promise ||
 - (
 - isObject(value) &&
 - typeof value.then === 'function' &&
 - typeof value.catch === 'function'
 - );
 - }
 
存在問題,你就不解決了嗎?不解決,等你來動(dòng)手。
基于訂閱發(fā)布模式的版本
這里是實(shí)現(xiàn)的另外一種思路, 利用訂閱發(fā)布者。
要點(diǎn)
通過在Promise監(jiān)聽EventEmitter事件, 這里因?yàn)橹恍枰O(jiān)聽一次,once閃亮登場。
- new Promise((resolve, reject) => {
 - emitter.once("initialized", () => {
 - resolve(instance);
 - });
 - emitter.once("error", (error) => {
 - reject(error);
 - });
 - });
 
實(shí)現(xiàn)代碼
這里就實(shí)現(xiàn)一個(gè)最基礎(chǔ)版本,至于帶上下文,參數(shù),超時(shí)的版本,大家可以嘗試自己實(shí)現(xiàn)。
- 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);
 - });
 - })
 - }
 - }
 
總結(jié)
異步單例不多見,這里要表達(dá)的是一種思想,把基于事件的編程,變?yōu)榛赑romise的編程。
這里其實(shí)還涉及一些設(shè)計(jì)模式, 學(xué)以致用,投入實(shí)際代碼中,解決問題,帶來收益,這才是我們追求的。
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















 
 
 









 
 
 
 