minigame-std
Version:
Cross-platform standard library for WeChat minigame and web browsers with unified APIs for crypto, fs, fetch, storage, and more.
1 lines • 8.43 kB
Source Map (JSON)
{"version":3,"file":"utils.mjs","names":[],"sources":["../src/std/utils/resultify.ts"],"sourcesContent":["import { Err, Ok, tryAsyncResult, type AsyncIOResult, type AsyncResult, type IOResult, type Result } from 'happy-rusty';\nimport { Future } from 'tiny-future';\nimport { miniGameFailureToError } from '../internal/helpers.js';\n\n/**\n * 将小游戏异步 API 转换为返回 `AsyncResult<T, E>` 的新函数,需要转换的 API 必须是接受可选 `success` 和 `fail` 回调的函数,并且其返回值必须是 `void` 或 `PromiseLike`。\n *\n * 其中 `T` 为 `success` 回调的参数类型,`E` 为 `fail` 回调的参数类型。\n *\n * @param api - 小游戏异步 API。\n * @returns 返回一个新的函数,该函数返回 `AsyncResult<T, E>`。\n * @since 2.0.0\n * @example\n * ```ts\n * // 将 wx.setStorage 转换为 AsyncResult 风格\n * const setStorageAsync = asyncResultify(wx.setStorage);\n * const result = await setStorageAsync({ key: 'test', data: 'value' });\n * if (result.isOk()) {\n * console.log('存储成功');\n * } else {\n * console.error('存储失败:', result.unwrapErr());\n * }\n * ```\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- 函数泛型约束需要 any 以兼容所有函数签名\nexport function asyncResultify<F extends (...args: any[]) => unknown, T = ResultifySuccessType<F>, E = ResultifyFailType<F>>(api: F): ResultifyValidAPI<F> extends true\n ? (...args: Parameters<F>) => AsyncResult<T, E>\n : never {\n return ((...args: Parameters<F>): AsyncResult<T, E> => {\n const future = new Future<Result<T, E>>();\n\n const options = args[0] ?? {};\n const { success, fail } = options;\n\n // 强制使用callback的方式调用,即使支持Promise\n options.success = (res: T) => {\n // success 回调依旧执行\n success?.(res);\n future.resolve(Ok(res));\n };\n options.fail = (err: E) => {\n // fail 回调依旧执行\n fail?.(err);\n future.resolve(Err(err));\n };\n\n const ret = api(options);\n\n // 也支持其他返回 PromiseLike 的 API(鸭子类型检查)\n if (ret != null && typeof ret === 'object' && typeof (ret as PromiseLike<T>).then === 'function') {\n // Convert PromiseLike to AsyncResult\n return tryAsyncResult(ret as PromiseLike<T>);\n } else if (ret !== undefined) {\n // 实测发现某些小游戏平台的 API 可能会返回非 void 非 PromiseLike 的值,虽然官方文档通常只描述了 void 或 PromiseLike 的返回类型。\n // 为了兼容这些实际情况,我们暂时不抛出错误,而是允许这种情况存在。\n // throw new TypeError('API must return void or PromiseLike. Otherwise the return value will be discarded');\n }\n\n return future.promise;\n }) as ResultifyValidAPI<F> extends true ? (...args: Parameters<F>) => AsyncResult<T, E> : never;\n}\n\n/**\n * `asyncResultify` 的变体,将小游戏异步 API 转换为返回 `AsyncIOResult<T>` 的新函数。\n *\n * 与 `asyncResultify` 不同的是,此函数会将 `fail` 回调的 `WechatMinigame.GeneralCallbackResult` 转换为 `Error` 类型。\n *\n * @param api - 小游戏异步 API。\n * @returns 返回一个新的函数,该函数返回 `AsyncIOResult<T>`。\n * @since 2.0.0\n * @example\n * ```ts\n * // 将 wx.setStorage 转换为 AsyncIOResult 风格\n * const setStorageAsync = asyncIOResultify(wx.setStorage);\n * const result = await setStorageAsync({ key: 'test', data: 'value' });\n * if (result.isOk()) {\n * console.log('存储成功');\n * } else {\n * console.error('存储失败:', result.unwrapErr().message);\n * }\n * ```\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- 函数泛型约束需要 any 以兼容所有函数签名\nexport function asyncIOResultify<F extends (...args: any[]) => unknown, T = ResultifySuccessType<F>>(api: F): IOResultifyValidAPI<F> extends true\n ? (...args: Parameters<F>) => AsyncIOResult<T>\n : never {\n const wrapped = asyncResultify<F, T, WechatMinigame.GeneralCallbackResult>(api);\n\n return (async (...args: Parameters<F>): AsyncIOResult<T> => {\n const result = await wrapped(...args);\n return result.mapErr(miniGameFailureToError);\n }) as IOResultifyValidAPI<F> extends true ? (...args: Parameters<F>) => AsyncIOResult<T> : never;\n}\n\n/**\n * 将小游戏同步 API 转换为返回 `IOResult<T>` 的新函数。\n *\n * 功能类似于 `tryGeneralSyncOp`,但以函数包装的方式使用,将可能抛出的异常捕获并转换为 `IOResult`。\n *\n * @param api - 小游戏同步 API。\n * @returns 返回一个新的函数,该函数返回 `IOResult<T>`。\n * @since 2.0.0\n * @example\n * ```ts\n * // 将 wx.getStorageSync 转换为 IOResult 风格\n * const getStorageSync = syncIOResultify(wx.getStorageSync);\n * const result = getStorageSync('test');\n * if (result.isOk()) {\n * console.log('获取成功:', result.unwrap());\n * } else {\n * console.error('获取失败:', result.unwrapErr().message);\n * }\n * ```\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- 函数泛型约束需要 any 以兼容所有函数签名\nexport function syncIOResultify<F extends (...args: any[]) => unknown>(api: F): (...args: Parameters<F>) => IOResult<ReturnType<F>> {\n return (...args: Parameters<F>): IOResult<ReturnType<F>> => {\n try {\n return Ok(api(...args) as ReturnType<F>);\n } catch (e) {\n return Err(miniGameFailureToError(e as WechatMinigame.GeneralCallbackResult));\n }\n };\n}\n\n// #region Internal Types\n\n/**\n * 通用回调函数类型。\n */\ntype AnyCallback = (...args: never[]) => unknown;\n\n/**\n * 类型工具:判断 API 是否符合 resultify 条件。\n *\n * 要求 API 返回 `void` 或 `PromiseLike`,且参数包含 `success` 或 `fail` 回调。\n * @typeParam T - 待检查的 API 函数类型。\n */\ntype ResultifyValidAPI<T> = T extends (params: infer P) => infer R\n ? R extends void | PromiseLike<unknown>\n ? P extends { success?: AnyCallback; } | undefined\n ? true\n : P extends { fail?: AnyCallback; } | undefined\n ? true\n : false\n : false\n : false;\n\n/**\n * 类型工具:判断 API 是否符合 asyncIOResultify 条件。\n *\n * 在 `ResultifyValidAPI` 基础上,额外要求 `fail` 回调参数类型必须精确为 `GeneralCallbackResult`。\n * @typeParam T - 待检查的 API 函数类型。\n */\ntype IOResultifyValidAPI<T> = ResultifyValidAPI<T> extends true\n ? ResultifyFailType<T> extends WechatMinigame.GeneralCallbackResult\n ? WechatMinigame.GeneralCallbackResult extends ResultifyFailType<T>\n ? true\n : false\n : false\n : false;\n\n/**\n * 类型工具:提取成功回调参数类型。\n *\n * 从 API 函数的 `success` 回调中提取返回类型。\n * @typeParam T - API 函数类型。\n */\ntype ResultifySuccessType<T> = T extends (params: infer P) => unknown\n ? P extends { success?: (res: infer S) => unknown; }\n ? S\n : never\n : never;\n\n/**\n * 类型工具:提取失败回调参数类型。\n *\n * 从 API 函数的 `fail` 回调中提取错误类型。\n * @typeParam T - API 函数类型。\n */\ntype ResultifyFailType<T> = T extends (params: infer P) => unknown\n ? P extends { fail?: (err: infer E) => unknown; }\n ? E\n : never\n : never;\n\n// #endregion\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAyBA,SAAgB,eAA6G,KAEjH;CACR,SAAS,GAAG,SAA2C;EACnD,MAAM,SAAS,IAAI,OAAqB;EAExC,MAAM,UAAU,KAAK,MAAM,CAAC;EAC5B,MAAM,EAAE,SAAS,SAAS;EAG1B,QAAQ,WAAW,QAAW;GAE1B,UAAU,GAAG;GACb,OAAO,QAAQ,GAAG,GAAG,CAAC;EAC1B;EACA,QAAQ,QAAQ,QAAW;GAEvB,OAAO,GAAG;GACV,OAAO,QAAQ,IAAI,GAAG,CAAC;EAC3B;EAEA,MAAM,MAAM,IAAI,OAAO;EAGvB,IAAI,OAAO,QAAQ,OAAO,QAAQ,YAAY,OAAQ,IAAuB,SAAS,YAElF,OAAO,eAAe,GAAqB;OACxC,IAAI,QAAQ,KAAA,GAAW,CAI9B;EAEA,OAAO,OAAO;CAClB;AACJ;;;;;;;;;;;;;;;;;;;;;AAuBA,SAAgB,iBAAqF,KAEzF;CACR,MAAM,UAAU,eAA2D,GAAG;CAE9E,QAAQ,OAAO,GAAG,SAA0C;EAExD,QAAO,MADc,QAAQ,GAAG,IAAI,EAAA,CACtB,OAAO,sBAAsB;CAC/C;AACJ;;;;;;;;;;;;;;;;;;;;;AAuBA,SAAgB,gBAAuD,KAA6D;CAChI,QAAQ,GAAG,SAAiD;EACxD,IAAI;GACA,OAAO,GAAG,IAAI,GAAG,IAAI,CAAkB;EAC3C,SAAS,GAAG;GACR,OAAO,IAAI,uBAAuB,CAAyC,CAAC;EAChF;CACJ;AACJ"}