如何通过消除双重等待和DRY代码来重构?
How to refactor by eliminating double await and the DRY code?
我怀疑哪种策略是管理此 Web 应用程序中众多服务客户端的最佳策略。
就用户设备 RAM 和 Javascript 执行速度(主线程操作)之间的良好折衷而言,“最佳”。
这就是我现在正在做的,这是主文件:
- main.ts:
import type { PlayerServiceClient } from './player.client';
import type { TeamServiceClient } from './team.client';
import type { RefereeServiceClient } from './referee.client';
import type { FriendServiceClient } from './friend.client';
import type { PrizeServiceClient } from './prize.client';
import type { WinnerServiceClient } from './winner.client';
import type { CalendarServiceClient } from './calendar.client';
let playerService: PlayerServiceClient;
export const player = async (): Promise<PlayerServiceClient> =>
playerService ||
((playerService = new (await import('./player.client')).PlayerServiceClient()),
playerService);
let teamService: TeamServiceClient;
export const getTeamService = (): TeamServiceClient =>
teamService ||
((teamService = new (await import('./team.client')).TeamServiceClient()),
teamService);
let refereeService: RefereeServiceClient;
export const getRefereeService = (): RefereeServiceClient =>
refereeService ||
((refereeService = new (await import('./referee.client')).RefereeServiceClient()),
refereeService);
let friendService: FriendServiceClient;
export const getFriendService = (): FriendServiceClient =>
friendService ||
((friendService = new (await import('./friend.client')).FriendServiceClient()),
friendService);
let prizeService: PrizeServiceClient;
export const getPrizeService = (): PrizeServiceClient =>
prizeService ||
((prizeService = new (await import('./prize.client')).PrizeServiceClient()),
prizeService);
let winnerService: WinnerServiceClient;
export const getWinnerService = (): WinnerServiceClient =>
winnerService ||
((winnerService = new (await import('./winner.client')).WinnerServiceClient()),
winnerService);
let calendarService: CalendarServiceClient;
export const getCalendarService = (): CalendarServiceClient =>
calendarService ||
((calendarService = new (await import('./calendar.client')).CalendarServiceClient()),
calendarService);
// and so on... a lot more...
如您所见,有许多个服务客户。
我正在使用此代码,因为我认为考虑到我的 Web 应用程序结构基于几乎与客户端服务重叠的路由,它更好:
我的意思是,如果播放器从 /home
转到 /players
页面,我可以这样使用它:
- components/players.苗条
import { getPlayerService } from "main";
const playerService = await getPlayerService();
const players = await playerService.queryPlayers();
这样,如果PlayerService
不存在,则此时导入并返回,否则为returns之前导入并实例化的
既然用户经常切换页面,这样我就可以避免那些客户端的突然创建和销毁,对吧?
但是通过这种方式,我使用了我不喜欢使用的 全局变量,并且我使用的是 冗长、枯燥和冗长的代码 在每个组件中。
有没有办法在组件中使用下面的代码?
import { playerService } from "main";
const players = await playerService.queryPlayers();
你建议我做什么?
您正在实施的模式是“延迟加载”和“单例”。
您可以拥有一个实现这些模式并将其用于每个服务的服务工厂:
文件serviceFactory.js
const serviceMap = {};
export function getService(serviceName) {
return serviceMap[serviceName] ?? (serviceMap[serviceName] = import(serviceName).then(x => new x.default));
}
ECMAScript 模块标准将负责在应用程序中仅执行一次 serviceFactory.js
代码(无论您导入它多少次),因此您可以将单例保存在分配给私有对象的映射中serviceFactory.js
模块的顶级变量。
此服务工厂意味着每个服务都使用 default
关键字导出,如下所示:
export default class SomeService {
constructor() {
// ...
}
fetchSomething() {
// ...
}
}
然后,使用以下代码在您的应用程序中随处使用服务:
import { getService } from './serviceFactory.js';
const service = await getService('./services/some.service.js');
const something = await service.fetchSomething();
如果你真的想去掉doubleawait
,你可以这样封装在服务工厂中:
const serviceMap = {};
export function getService(serviceName) {
return serviceMap[serviceName] ?? (serviceMap[serviceName] = resolveService(serviceName));
}
function resolveService(name) {
const futureInstance = import(name).then(x => new x.default);
const handler = {
get: function (target, prop) {
return function (...args) {
return target.then(instance => instance[prop](...args));
}
}
}
return new Proxy(futureInstance, handler);
}
允许您编写以下代码:
const something = await getService('./services/some.service.js').fetchSomething();
这允许在您需要的确切代码行加载服务。
如果因为需要 import { playerService } from "main";
语法而使用静态 import
加载它不会打扰您,您可以在 每个服务一个文件 :
export const playerService = getService('./services/player.service.js');
我在这里发布了完整的工作演示:https://github.com/Guerric-P/lazy-singletons-demo
我认为来自@Guerric 的代码无法与构建工具(如 webpack)一起使用。
特别是不支持动态字符串导入 import(modulePath)
。
我的建议是将重复的代码位减少到它们的最小表示...希望它最终会感觉不那么嘈杂。
解决方案#1/2
下面是一个使用高阶 memoize
函数来帮助缓存的示例。
// Minimal definition of service loaders
export const getPlayerService = memoize<PlayerServiceClient>(async () => new (await import('./player.client')).PlayerServiceClient());
export const getTeamService = memoize<TeamServiceClient>(async () => new (await import('./team.client')).TeamServiceClient());
export const getRefereeService = memoize<RefereeServiceClient>(async () => new (await import('./referee.client')).RefereeServiceClient());
export const getFriendService = memoize<FriendServiceClient>(async () => new (await import('./friend.client')).FriendServiceClient());
export const getPrizeService = memoize<PrizeServiceClient>(async () => new (await import('./prize.client')).PrizeServiceClient());
export const getWinnerService = memoize<WinnerServiceClient>(async () => new (await import('./winner.client')).WinnerServiceClient());
// Mock hacked together memoize fn
// TODO: Replace with some npm library alternative
const fnCache = new WeakMap();
function memoize<TReturn>(fn): TReturn {
let cachedValue = fnCache.get(fn);
if (cachedValue) return cachedValue;
cachedValue = fn();
fnCache.set(fn, cachedValue);
return cachedValue;
}
解决方案#2/2
根据 JS 引擎和转译器的版本,您可能会删除一些代码并使用模块的性质来缓存服务的单例。
(注意:我偶尔会 运行 陷入 ES 模块如何依赖确定性导出的陷阱。解决方法是将导出分配给 return 实例的未决承诺。)
了解 Promises 的重要特性:它们只被解析一次,并且可以用来有效地缓存它们的结果。
每个 await
或 .then
将获得初始解析值。
// SUPER minimal definition of services
export const playerService = (async (): PlayerServiceClient => new (await import('./player.client')).PlayerServiceClient())();
export const teamService = (async (): TeamServiceClient => new (await import('./team.client')).TeamServiceClient())();
export const refereeService = (async (): RefereeServiceClient => new (await import('./referee.client')).RefereeServiceClient())();
export const friendService = (async (): FriendServiceClient => new (await import('./friend.client')).FriendServiceClient())();
export const prizeService = (async (): PrizeServiceClient => new (await import('./prize.client')).PrizeServiceClient())();
export const winnerService = (async (): WinnerServiceClient => new (await import('./winner.client')).WinnerServiceClient())();
调用服务包装器
import { playerService } from "./services";
// Example: Using async/await IIFE
const PlayerService = (async () => await playerService)();
function async App() {
// Example: Function-scoped service instance:
// const PlayerService = await playerService
const players = await PlayerService.queryPlayers();
}
我怀疑哪种策略是管理此 Web 应用程序中众多服务客户端的最佳策略。
就用户设备 RAM 和 Javascript 执行速度(主线程操作)之间的良好折衷而言,“最佳”。
这就是我现在正在做的,这是主文件:
- main.ts:
import type { PlayerServiceClient } from './player.client';
import type { TeamServiceClient } from './team.client';
import type { RefereeServiceClient } from './referee.client';
import type { FriendServiceClient } from './friend.client';
import type { PrizeServiceClient } from './prize.client';
import type { WinnerServiceClient } from './winner.client';
import type { CalendarServiceClient } from './calendar.client';
let playerService: PlayerServiceClient;
export const player = async (): Promise<PlayerServiceClient> =>
playerService ||
((playerService = new (await import('./player.client')).PlayerServiceClient()),
playerService);
let teamService: TeamServiceClient;
export const getTeamService = (): TeamServiceClient =>
teamService ||
((teamService = new (await import('./team.client')).TeamServiceClient()),
teamService);
let refereeService: RefereeServiceClient;
export const getRefereeService = (): RefereeServiceClient =>
refereeService ||
((refereeService = new (await import('./referee.client')).RefereeServiceClient()),
refereeService);
let friendService: FriendServiceClient;
export const getFriendService = (): FriendServiceClient =>
friendService ||
((friendService = new (await import('./friend.client')).FriendServiceClient()),
friendService);
let prizeService: PrizeServiceClient;
export const getPrizeService = (): PrizeServiceClient =>
prizeService ||
((prizeService = new (await import('./prize.client')).PrizeServiceClient()),
prizeService);
let winnerService: WinnerServiceClient;
export const getWinnerService = (): WinnerServiceClient =>
winnerService ||
((winnerService = new (await import('./winner.client')).WinnerServiceClient()),
winnerService);
let calendarService: CalendarServiceClient;
export const getCalendarService = (): CalendarServiceClient =>
calendarService ||
((calendarService = new (await import('./calendar.client')).CalendarServiceClient()),
calendarService);
// and so on... a lot more...
如您所见,有许多个服务客户。
我正在使用此代码,因为我认为考虑到我的 Web 应用程序结构基于几乎与客户端服务重叠的路由,它更好:
我的意思是,如果播放器从 /home
转到 /players
页面,我可以这样使用它:
- components/players.苗条
import { getPlayerService } from "main";
const playerService = await getPlayerService();
const players = await playerService.queryPlayers();
这样,如果PlayerService
不存在,则此时导入并返回,否则为returns之前导入并实例化的
既然用户经常切换页面,这样我就可以避免那些客户端的突然创建和销毁,对吧?
但是通过这种方式,我使用了我不喜欢使用的 全局变量,并且我使用的是 冗长、枯燥和冗长的代码 在每个组件中。
有没有办法在组件中使用下面的代码?
import { playerService } from "main";
const players = await playerService.queryPlayers();
你建议我做什么?
您正在实施的模式是“延迟加载”和“单例”。
您可以拥有一个实现这些模式并将其用于每个服务的服务工厂:
文件serviceFactory.js
const serviceMap = {};
export function getService(serviceName) {
return serviceMap[serviceName] ?? (serviceMap[serviceName] = import(serviceName).then(x => new x.default));
}
ECMAScript 模块标准将负责在应用程序中仅执行一次 serviceFactory.js
代码(无论您导入它多少次),因此您可以将单例保存在分配给私有对象的映射中serviceFactory.js
模块的顶级变量。
此服务工厂意味着每个服务都使用 default
关键字导出,如下所示:
export default class SomeService {
constructor() {
// ...
}
fetchSomething() {
// ...
}
}
然后,使用以下代码在您的应用程序中随处使用服务:
import { getService } from './serviceFactory.js';
const service = await getService('./services/some.service.js');
const something = await service.fetchSomething();
如果你真的想去掉doubleawait
,你可以这样封装在服务工厂中:
const serviceMap = {};
export function getService(serviceName) {
return serviceMap[serviceName] ?? (serviceMap[serviceName] = resolveService(serviceName));
}
function resolveService(name) {
const futureInstance = import(name).then(x => new x.default);
const handler = {
get: function (target, prop) {
return function (...args) {
return target.then(instance => instance[prop](...args));
}
}
}
return new Proxy(futureInstance, handler);
}
允许您编写以下代码:
const something = await getService('./services/some.service.js').fetchSomething();
这允许在您需要的确切代码行加载服务。
如果因为需要 import { playerService } from "main";
语法而使用静态 import
加载它不会打扰您,您可以在 每个服务一个文件 :
export const playerService = getService('./services/player.service.js');
我在这里发布了完整的工作演示:https://github.com/Guerric-P/lazy-singletons-demo
我认为来自@Guerric 的代码无法与构建工具(如 webpack)一起使用。
特别是不支持动态字符串导入 import(modulePath)
。
我的建议是将重复的代码位减少到它们的最小表示...希望它最终会感觉不那么嘈杂。
解决方案#1/2
下面是一个使用高阶 memoize
函数来帮助缓存的示例。
// Minimal definition of service loaders
export const getPlayerService = memoize<PlayerServiceClient>(async () => new (await import('./player.client')).PlayerServiceClient());
export const getTeamService = memoize<TeamServiceClient>(async () => new (await import('./team.client')).TeamServiceClient());
export const getRefereeService = memoize<RefereeServiceClient>(async () => new (await import('./referee.client')).RefereeServiceClient());
export const getFriendService = memoize<FriendServiceClient>(async () => new (await import('./friend.client')).FriendServiceClient());
export const getPrizeService = memoize<PrizeServiceClient>(async () => new (await import('./prize.client')).PrizeServiceClient());
export const getWinnerService = memoize<WinnerServiceClient>(async () => new (await import('./winner.client')).WinnerServiceClient());
// Mock hacked together memoize fn
// TODO: Replace with some npm library alternative
const fnCache = new WeakMap();
function memoize<TReturn>(fn): TReturn {
let cachedValue = fnCache.get(fn);
if (cachedValue) return cachedValue;
cachedValue = fn();
fnCache.set(fn, cachedValue);
return cachedValue;
}
解决方案#2/2
根据 JS 引擎和转译器的版本,您可能会删除一些代码并使用模块的性质来缓存服务的单例。
(注意:我偶尔会 运行 陷入 ES 模块如何依赖确定性导出的陷阱。解决方法是将导出分配给 return 实例的未决承诺。)
了解 Promises 的重要特性:它们只被解析一次,并且可以用来有效地缓存它们的结果。
每个 await
或 .then
将获得初始解析值。
// SUPER minimal definition of services
export const playerService = (async (): PlayerServiceClient => new (await import('./player.client')).PlayerServiceClient())();
export const teamService = (async (): TeamServiceClient => new (await import('./team.client')).TeamServiceClient())();
export const refereeService = (async (): RefereeServiceClient => new (await import('./referee.client')).RefereeServiceClient())();
export const friendService = (async (): FriendServiceClient => new (await import('./friend.client')).FriendServiceClient())();
export const prizeService = (async (): PrizeServiceClient => new (await import('./prize.client')).PrizeServiceClient())();
export const winnerService = (async (): WinnerServiceClient => new (await import('./winner.client')).WinnerServiceClient())();
调用服务包装器
import { playerService } from "./services";
// Example: Using async/await IIFE
const PlayerService = (async () => await playerService)();
function async App() {
// Example: Function-scoped service instance:
// const PlayerService = await playerService
const players = await PlayerService.queryPlayers();
}