Merge pull request #1347 from Rintim/Dev-Enhancement-UniteResource

重新制定`lib.init.parseResourceAddress`的API
This commit is contained in:
Spmario233 2024-05-13 15:07:44 +08:00 committed by GitHub
commit b6dbe2a135
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 122 additions and 91 deletions

View File

@ -1461,7 +1461,10 @@ export class Game {
* @returns { string[] } 语音地址列表 * @returns { string[] } 语音地址列表
*/ */
parseSkillText(skill, player, skillInfo) { parseSkillText(skill, player, skillInfo) {
return game.parseSkillTextMap(skill, player, skillInfo).map(data => data.text).filter(Boolean); return game
.parseSkillTextMap(skill, player, skillInfo)
.map(data => data.text)
.filter(Boolean);
} }
/** /**
* 根据skill中的audio,audioname,audioname2和player来获取技能台词列表及其对应的源文件名 * 根据skill中的audio,audioname,audioname2和player来获取技能台词列表及其对应的源文件名
@ -1489,19 +1492,22 @@ export class Game {
const getName = filter => { const getName = filter => {
const name = (player.tempname || []).find(i => filter(i)); const name = (player.tempname || []).find(i => filter(i));
if (name) return name; if (name) return name;
return [player.name, player.name1, player.name2].reduce((result, name) => { return [player.name, player.name1, player.name2].reduce(
if (result) return result; (result, name) => {
if (!name) return result; if (result) return result;
if (filter(name)) return name; if (!name) return result;
return get.character(name).tempname.find(i => filter(i)) || result; if (filter(name)) return name;
}, void 0); return get.character(name).tempname.find(i => filter(i)) || result;
},
void 0
);
}; };
const getTextMap = (path, name, ext, isDefault) => ({ const getTextMap = (path, name, ext, isDefault) => ({
name, name,
file: `${path}${name}${ext}`, file: `${path}${name}${ext}`,
text: lib.translate[`#${name}`], text: lib.translate[`#${name}`],
isDefault isDefault,
}); });
const getAudioList = (skill, options, skillInfo) => { const getAudioList = (skill, options, skillInfo) => {
@ -1518,7 +1524,7 @@ export class Game {
if (info.audioname2) audioInfo = info.audioname2[getName(i => info.audioname2[i])] || audioInfo; if (info.audioname2) audioInfo = info.audioname2[getName(i => info.audioname2[i])] || audioInfo;
return parseAudio(skill, options, audioInfo); return parseAudio(skill, options, audioInfo);
} };
const parseAudio = (skill, options, audioInfo) => { const parseAudio = (skill, options, audioInfo) => {
const audioname = options.audioname.slice(); const audioname = options.audioname.slice();
@ -1533,8 +1539,8 @@ export class Game {
} }
const map = {}; const map = {};
audioInfo.forEach((i) => { audioInfo.forEach(i => {
parseAudio(skill, options, i).forEach(data => map[data.name] = data); parseAudio(skill, options, i).forEach(data => (map[data.name] = data));
}); });
return Object.values(map); return Object.values(map);
} }
@ -1551,9 +1557,7 @@ export class Game {
let [, path = "skill", audioNum, ext = "mp3"] = list; let [, path = "skill", audioNum, ext = "mp3"] = list;
let _audioname = getName(i => audioname.includes(i)); let _audioname = getName(i => audioname.includes(i));
_audioname = _audioname ? `_${_audioname}` : ""; _audioname = _audioname ? `_${_audioname}` : "";
if (audioNum === "true") return [getTextMap(`${path}/`, `${skill}${_audioname}`, `.${ext}`, isDefault)]; if (audioNum === "true") return [getTextMap(`${path}/`, `${skill}${_audioname}`, `.${ext}`, isDefault)];
const audioList = []; const audioList = [];
audioNum = parseInt(audioNum); audioNum = parseInt(audioNum);
for (let i = 1; i <= audioNum; i++) { for (let i = 1; i <= audioNum; i++) {
@ -1584,16 +1588,15 @@ export class Game {
if (typeof player !== "string" && player.skin && player.skin.name) { if (typeof player !== "string" && player.skin && player.skin.name) {
const skinName = player.skin.name; const skinName = player.skin.name;
if (skinName !== name && lib.characterSubstitute[name]) { if (skinName !== name && lib.characterSubstitute[name]) {
const skin = lib.characterSubstitute[name].find((i) => i[0] === skinName); const skin = lib.characterSubstitute[name].find(i => i[0] === skinName);
if (skin) { if (skin) {
const newCharacter = get.convertedCharacter(['', '', 0, [], skin[1]]); const newCharacter = get.convertedCharacter(["", "", 0, [], skin[1]]);
name = skinName; name = skinName;
audioInfo = newCharacter.dieAudios; audioInfo = newCharacter.dieAudios;
} }
} }
} }
const defaultInfo = true; const defaultInfo = true;
const check = (name, history) => { const check = (name, history) => {
@ -1608,7 +1611,7 @@ export class Game {
name, name,
file: `${path}${name}${ext}`, file: `${path}${name}${ext}`,
text: lib.translate[`#${name}:die`], text: lib.translate[`#${name}:die`],
isDefault isDefault,
}); });
const getAudioList = (name, options, audioInfo) => { const getAudioList = (name, options, audioInfo) => {
@ -1627,7 +1630,7 @@ export class Game {
history.unshift(name); history.unshift(name);
return parseAudio(name, options, audioInfo); return parseAudio(name, options, audioInfo);
} };
const parseAudio = (name, options, audioInfo) => { const parseAudio = (name, options, audioInfo) => {
const history = options.history.slice(); const history = options.history.slice();
@ -1641,8 +1644,8 @@ export class Game {
// } // }
const map = {}; const map = {};
audioInfo.forEach((i) => { audioInfo.forEach(i => {
parseAudio(name, options, i).forEach(data => map[data.name] = data); parseAudio(name, options, i).forEach(data => (map[data.name] = data));
}); });
return Object.values(map); return Object.values(map);
} }
@ -1668,14 +1671,15 @@ export class Game {
return audioList; return audioList;
} }
let path = "", ext = ""; let path = "",
ext = "";
if (!/^db:|^ext:|\//.test(audioInfo)) path = "die/"; if (!/^db:|^ext:|\//.test(audioInfo)) path = "die/";
if (!/\.\w+$/.test(audioInfo)) ext = ".mp3"; if (!/\.\w+$/.test(audioInfo)) ext = ".mp3";
if (path && ext) return parseAudio(audioInfo, Object.assign(options, { isDefault: true }), defaultInfo); if (path && ext) return parseAudio(audioInfo, Object.assign(options, { isDefault: true }), defaultInfo);
//@TODO //@TODO
console.warn(`${name}中的地址写法(${audioInfo})暂时没有完全支持台词系统。`); console.warn(`${name}中的地址写法(${audioInfo})暂时没有完全支持台词系统。`);
return [getTextMap(path, audioInfo, ext, isDefault)]; return [getTextMap(path, audioInfo, ext, isDefault)];
} };
return getAudioList(name, { history: [], isDefault: false }, audioInfo); return getAudioList(name, { history: [], isDefault: false }, audioInfo);
} }
@ -1695,8 +1699,8 @@ export class Game {
if (!lib.config.background_speak) return; if (!lib.config.background_speak) return;
if (info.direct && !directaudio) return; if (info.direct && !directaudio) return;
if (lib.skill.global.includes(skill) && !info.forceaudio) return; if (lib.skill.global.includes(skill) && !info.forceaudio) return;
let audio,
let audio, list = game.parseSkillTextMap(skill, player, skillInfo).randomSort(); list = game.parseSkillTextMap(skill, player, skillInfo).randomSort();
return (function play() { return (function play() {
if (!list.length) return; if (!list.length) return;
audio = list.shift(); audio = list.shift();
@ -1716,7 +1720,9 @@ export class Game {
else if (player.skin && player.skin.name) playerName = player.skin.name; else if (player.skin && player.skin.name) playerName = player.skin.name;
else playerName = player.name; else playerName = player.name;
let audio, isDefault, list = game.parseDieTextMap(player).randomSort(); let audio,
isDefault,
list = game.parseDieTextMap(player).randomSort();
const check = () => { const check = () => {
if (list.length) return true; if (list.length) return true;
if (!audio) return false; if (!audio) return false;
@ -1725,7 +1731,7 @@ export class Game {
playerName = playerName.slice(playerName.indexOf("_") + 1); playerName = playerName.slice(playerName.indexOf("_") + 1);
list = game.parseDieTextMap(playerName).randomSort(); list = game.parseDieTextMap(playerName).randomSort();
return check(); return check();
} };
return (function play() { return (function play() {
if (!check()) return; if (!check()) return;
audio = list.shift(); audio = list.shift();
@ -3416,8 +3422,7 @@ export class Game {
}; };
// player.removeGaintag.apply(player, content); // player.removeGaintag.apply(player, content);
checkMatch(content[1], player.getCards("h")); checkMatch(content[1], player.getCards("h"));
} } else player.removeGaintag(content);
else player.removeGaintag(content);
} else { } else {
console.log(player); console.log(player);
} }
@ -5906,19 +5911,19 @@ export class Game {
// 数组形式 // 数组形式
if ("contents" in event && Array.isArray(event.contents)) { if ("contents" in event && Array.isArray(event.contents)) {
/* /*
event.contents[step](event, trigger, player, _storeEvent).then((evt) => { event.contents[step](event, trigger, player, _storeEvent).then((evt) => {
if (evt) event._storeEvent = evt; if (evt) event._storeEvent = evt;
if (game.executingAsyncEventMap.has(event.toEvent())) { if (game.executingAsyncEventMap.has(event.toEvent())) {
game.executingAsyncEventMap.set(_status.event.toEvent(), game.executingAsyncEventMap.get(_status.event.toEvent()).then(() => { game.executingAsyncEventMap.set(_status.event.toEvent(), game.executingAsyncEventMap.get(_status.event.toEvent()).then(() => {
if (event.step >= event.contents.length - 1) event.finish(); if (event.step >= event.contents.length - 1) event.finish();
resolve(); resolve();
})); }));
} else { } else {
if (event.step >= event.contents.length - 1) event.finish(); if (event.step >= event.contents.length - 1) event.finish();
resolve(); resolve();
} }
}); });
*/ */
// 解决不了问题...就把问题统一 // 解决不了问题...就把问题统一
const run = async event => { const run = async event => {
if (typeof event.step !== "number") event.step = 0; if (typeof event.step !== "number") event.step = 0;
@ -7082,7 +7087,7 @@ export class Game {
for (let i = 0; i < event.config.size; i++) { for (let i = 0; i < event.config.size; i++) {
ui.window.appendChild(event.nodes[i]); ui.window.appendChild(event.nodes[i]);
} }
"step 1"; ("step 1");
let rand1 = event.config.first; let rand1 = event.config.first;
if (rand1 == "rand") { if (rand1 == "rand") {
rand1 = Math.random() < 0.5; rand1 = Math.random() < 0.5;
@ -7119,7 +7124,7 @@ export class Game {
} }
game.delay(); game.delay();
lib.init.onfree(); lib.init.onfree();
"step 2"; ("step 2");
if (event.checkredo()) return; if (event.checkredo()) return;
if (event._skiprest) return; if (event._skiprest) return;
if (event.side < 2) { if (event.side < 2) {
@ -7135,7 +7140,7 @@ export class Game {
event.aiMove(); event.aiMove();
game.delay(); game.delay();
} }
"step 3"; ("step 3");
if (typeof event.fast == "number" && get.time() - event.fast <= 1000) { if (typeof event.fast == "number" && get.time() - event.fast <= 1000) {
event.fast = true; event.fast = true;
} else { } else {
@ -7170,7 +7175,7 @@ export class Game {
game.delay(); game.delay();
} }
} }
"step 4"; ("step 4");
if (event.checkredo()) return; if (event.checkredo()) return;
if (event.skipnode) event.skipnode.delete(); if (event.skipnode) event.skipnode.delete();
if (event.replacenode) event.replacenode.delete(); if (event.replacenode) event.replacenode.delete();
@ -7189,7 +7194,7 @@ export class Game {
} }
} }
game.delay(); game.delay();
"step 5"; ("step 5");
event.prompt("选择" + get.cnNumber(event.config.num) + "名出场武将"); event.prompt("选择" + get.cnNumber(event.config.num) + "名出场武将");
event.enemylist = []; event.enemylist = [];
for (let i = 0; i < event.avatars.length; i++) { for (let i = 0; i < event.avatars.length; i++) {
@ -7219,7 +7224,7 @@ export class Game {
event.nodes[i].hide(); event.nodes[i].hide();
} }
game.pause(); game.pause();
"step 6"; ("step 6");
event.promptbar.delete(); event.promptbar.delete();
if (ui.cardPileButton) ui.cardPileButton.style.display = ""; if (ui.cardPileButton) ui.cardPileButton.style.display = "";
lib.onresize.remove(event.resize); lib.onresize.remove(event.resize);
@ -7926,7 +7931,7 @@ export class Game {
game.reload2(); game.reload2();
resolve(result); resolve(result);
}; };
} }
: (resolve, reject) => { : (resolve, reject) => {
lib.status.reload++; lib.status.reload++;
const idbRequest = lib.db.transaction([storeName], "readwrite").objectStore(storeName).openCursor(), const idbRequest = lib.db.transaction([storeName], "readwrite").objectStore(storeName).openCursor(),
@ -7956,7 +7961,7 @@ export class Game {
game.reload2(); game.reload2();
resolve(object); resolve(object);
}; };
} }
); );
} }
/** /**
@ -8010,7 +8015,7 @@ export class Game {
game.reload2(); game.reload2();
resolve(event); resolve(event);
}; };
}) })
: game.getDB(storeName).then(object => { : game.getDB(storeName).then(object => {
const keys = Object.keys(object); const keys = Object.keys(object);
lib.status.reload += keys.length; lib.status.reload += keys.length;
@ -8031,7 +8036,7 @@ export class Game {
}) })
) )
); );
}); });
} }
/** /**
* @param { string } key * @param { string } key

View File

@ -216,7 +216,7 @@ export class Get {
//装备栏 END //装备栏 END
/** /**
* @param {string} chinese * @param {string} chinese
* @param {boolean|undefined} withTone * @param {boolean|undefined} withTone
* @returns { any[] } * @returns { any[] }
*/ */
pinyin(chinese, withTone) { pinyin(chinese, withTone) {
@ -224,10 +224,9 @@ export class Get {
const pinyins = lib.pinyins; const pinyins = lib.pinyins;
if (pinyins && pinyins[chinese] && Array.isArray(pinyins[chinese])) { if (pinyins && pinyins[chinese] && Array.isArray(pinyins[chinese])) {
result = pinyins[chinese].slice(0); result = pinyins[chinese].slice(0);
} } else {
else {
//@ts-ignore //@ts-ignore
result = pinyinPro.pinyin(chinese, {type: "array"}); result = pinyinPro.pinyin(chinese, { type: "array" });
} }
//@ts-ignore //@ts-ignore
if (withTone === false) result = pinyinPro.convert(result, { format: "toneNone" }); if (withTone === false) result = pinyinPro.convert(result, { format: "toneNone" });
@ -295,7 +294,7 @@ export class Get {
*/ */
yunjiao(str) { yunjiao(str) {
//@ts-ignore //@ts-ignore
str = pinyinPro.convert(str, { format: "toneNone" }) str = pinyinPro.convert(str, { format: "toneNone" });
if (lib.pinyins._metadata.zhengtirendu.includes(str)) { if (lib.pinyins._metadata.zhengtirendu.includes(str)) {
str = "-" + str[str.length - 1]; str = "-" + str[str.length - 1];
} else { } else {
@ -4601,7 +4600,8 @@ export class Get {
) )
temp2 = cache.delegate(temp2.effect).target(card, player, target, result2, isLink); temp2 = cache.delegate(temp2.effect).target(card, player, target, result2, isLink);
else temp2 = undefined; else temp2 = undefined;
} else if (typeof temp2.effect == "function") { //考虑废弃 } else if (typeof temp2.effect == "function") {
//考虑废弃
console.log("此写法使用频率极低且影响代码可读性,不建议使用"); console.log("此写法使用频率极低且影响代码可读性,不建议使用");
if ( if (
!player.hasSkillTag("ignoreSkill", true, { !player.hasSkillTag("ignoreSkill", true, {
@ -4832,7 +4832,7 @@ export class Get {
* *
* @async * @async
* @param {Blob} blob - 需要转换的内容 * @param {Blob} blob - 需要转换的内容
* @returns {Promise<string>} 对应Blob内容的 * @returns {Promise<URL>} 对应Blob内容的
* *
* @example * @example
* let text = "Hello, World!"; * let text = "Hello, World!";
@ -4840,7 +4840,7 @@ export class Get {
* *
* let blob = new Blob([text], { type: "text/plain" }); * let blob = new Blob([text], { type: "text/plain" });
* let url = await get.dataUrlAsync(blob); * let url = await get.dataUrlAsync(blob);
* console.assert("data:text/plain;base64,SGVsbG8sIFdvcmxkIQ=="); * console.assert(url.href === "data:text/plain;base64,SGVsbG8sIFdvcmxkIQ==");
*/ */
dataUrlAsync(blob) { dataUrlAsync(blob) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@ -4848,11 +4848,13 @@ export class Get {
fileReader.onload = resolve; fileReader.onload = resolve;
fileReader.onerror = reject; fileReader.onerror = reject;
fileReader.readAsDataURL(blob); fileReader.readAsDataURL(blob);
}).then(event => event.target.result); }).then(event => new URL(event.target.result));
} }
/** /**
* 通过`fetch`读取data URL的内容转换成Blob后返回生成的blob URL * 通过`Get#blobFromUrl`读取data URL的内容转换成Blob后返回生成的blob URL
*
* > 实际上所有的URL都能通过此方法读取
* *
* 该方法具有缓存同一data URL仅会返回同一blob URL * 该方法具有缓存同一data URL仅会返回同一blob URL
* *
@ -4866,16 +4868,30 @@ export class Get {
* @param {string | URL} dataUrl - 需要转换的data URL * @param {string | URL} dataUrl - 需要转换的data URL
* @returns {Promise<URL>} * @returns {Promise<URL>}
*/ */
async objectURLAsync(dataUrl) { async objectUrlAsync(dataUrl) {
let dataString = dataUrl instanceof URL ? dataUrl.href : dataUrl; let dataString = dataUrl instanceof URL ? dataUrl.href : dataUrl;
const objectURLMap = lib.objectURL; const objectURLMap = lib.objectURL;
if (objectURLMap.has(dataString)) return new URL(objectURLMap.get(dataString)); if (objectURLMap.has(dataString)) return new URL(objectURLMap.get(dataString));
let blob = await (await fetch(dataUrl)).blob(); let blob = await this.blobFromUrl(dataUrl);
const objectURL = URL.createObjectURL(blob); const objectURL = URL.createObjectURL(blob);
objectURLMap.set(dataString, objectURL); objectURLMap.set(dataString, objectURL);
return new URL(objectURL); return new URL(objectURL);
} }
/**
* 读取给定的URL将其中的内容转换成Blob
*
* 在File协议下通过无名杀自带的文件处理函数读取内容其他协议通过`fetch`读取内容
*
* @async
* @param {string | URL} url - 需要读取的URL
* @returns {Promise<Blob>}
*/
blobFromUrl(url) {
let link = url instanceof URL ? url : new URL(url);
return link.protocol == "file:" ? game.promises.readFile(get.relativePath(link)).then(buffer => new Blob([buffer])) : fetch(link).then(response => response.blob());
}
} }
export let get = new Get(); export let get = new Get();

View File

@ -838,18 +838,24 @@ export class LibInit {
} }
/** /**
* @async
* @param {string | URL} link - 需要解析的路径 * @param {string | URL} link - 需要解析的路径
* @param {((item: string) => string) | null} [defaultHandle] - 在给定路径不符合可用情况或基于无名杀相关默认情况处理路径的函数返回的路径应是相对于根目录的相对路径默认为`null`当且仅当无法解析成`URL`时会调用该回调 * @param {((item: string) => string) | null} [defaultHandle] - 在给定路径不符合可用情况或基于无名杀相关默认情况处理路径的函数返回的路径应是相对于根目录的相对路径默认为`null`当且仅当无法解析成`URL`时会调用该回调
* @param {boolean} [forceLoadAsDataUrl] - 是否将资源加载为[Data URL](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Basics_of_HTTP/Data_URLs)默认为`false` * @param {((item: URL) => unknown) | null} [loadAsDataUrlCallback] - 若存在值将资源加载为[Data URL](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Basics_of_HTTP/Data_URLs)然后传入进回调函数
* @param {boolean} [dbNow] - 此刻是否在解析数据库中的内容请勿直接使用 * @param {boolean} [dbNow] - 此刻是否在解析数据库中的内容请勿直接使用
* @returns {Promise<URL>} * @returns {URL}
*/ */
async parseResourceAddress(link, defaultHandle = null, forceLoadAsDataUrl = false, dbNow = false) { parseResourceAddress(link, defaultHandle = null, loadAsDataUrlCallback = null, dbNow = false) {
// 适当的摆了,中文错误应该没人会反对
if (!link) throw new Error(dbNow ? "传入的数据库链接中不存在内容" : "请传入需要解析的链接");
let linkString = link instanceof URL ? link.href : link; let linkString = link instanceof URL ? link.href : link;
// 如果传入值为Data URL经过分析可知无需处理故直接返回成品URL // 如果传入值为Data URL经过分析可知无需处理故直接返回成品URL
if (linkString.startsWith("data:")) return new URL(linkString); if (linkString.startsWith("data:")) {
let result = new URL(linkString);
if (loadAsDataUrlCallback) loadAsDataUrlCallback(result);
return result;
}
/** /**
* @type {URL} * @type {URL}
@ -862,37 +868,24 @@ export class LibInit {
resultUrl = new URL(linkString); resultUrl = new URL(linkString);
} else if (dbNow) { } else if (dbNow) {
let content = new Blob([linkString], { type: "text/plain" }); let content = new Blob([linkString], { type: "text/plain" });
resultUrl = new URL(await get.dataUrlAsync(content)); get.dataUrlAsync(content).then(loadAsDataUrlCallback);
// @ts-expect-error 此处的返回值无任何用处
return;
} else { } else {
let resultLink = defaultHandle == null ? linkString : defaultHandle(linkString); let resultLink = defaultHandle == null ? linkString : defaultHandle(linkString);
resultUrl = new URL(resultLink, rootURL); resultUrl = new URL(resultLink, rootURL);
} }
if (forceLoadAsDataUrl && !resultUrl.href.startsWith("data:")) { if (loadAsDataUrlCallback != null) {
if (linkString.startsWith("db:")) { if (resultUrl.protocol == "db:") {
/**
* @type {string}
*/
let storeResult = await game.getDB("image", linkString.slice(3));
// 我思索了一下,如果这玩意能造成无限递归 // 我思索了一下,如果这玩意能造成无限递归
// 那么我只能说,你赢了 // 那么我只能说,你赢了
return this.parseResourceAddress(storeResult, defaultHandle, forceLoadAsDataUrl, true); game.getDB("image", linkString.slice(3)).then(storeResult => this.parseResourceAddress(storeResult, defaultHandle, loadAsDataUrlCallback, true));
}
/**
* @type {Blob}
*/
let blob;
if (linkString.startsWith("file:")) {
let buffer = await game.promises.readFile(get.relativePath(resultUrl));
blob = new Blob([buffer]);
} else { } else {
let response = await fetch(resultUrl.href); get.blobFromUrl(resultUrl)
blob = await response.blob(); .then(blob => get.dataUrlAsync(blob))
.then(loadAsDataUrlCallback);
} }
resultUrl.href = await get.dataUrlAsync(blob);
} }
return resultUrl; return resultUrl;

View File

@ -72,6 +72,23 @@ export class LibInitPromises {
* @returns {Promise<URL>} * @returns {Promise<URL>}
*/ */
parseResourceAddress(link, defaultHandle = null, forceLoadAsDataUrl = false) { parseResourceAddress(link, defaultHandle = null, forceLoadAsDataUrl = false) {
return lib.init.parseResourceAddress(link, defaultHandle, forceLoadAsDataUrl); if (!forceLoadAsDataUrl) return Promise.resolve(lib.init.parseResourceAddress(link, defaultHandle));
let { promise, resolve } = Promise.withResolvers();
lib.init.parseResourceAddress(link, defaultHandle, result => resolve(result));
return promise;
}
/**
* @async
* @param {string | URL} link - 需要解析的路径
* @param {((item: string) => string) | null} [defaultHandle] - 在给定路径不符合可用情况或基于无名杀相关默认情况处理路径的函数返回的路径应是相对于根目录的相对路径默认为`null`当且仅当无法解析成`URL`时会调用该回调
* @returns {Promise<[origin: URL, data: URL]>}
*/
async parseResourceAddressExt(link, defaultHandle = null) {
let { promise, resolve } = Promise.withResolvers();
let origin = lib.init.parseResourceAddress(link, defaultHandle, result => resolve(result));
return [origin, await promise];
} }
} }