fix(web): recover transient map SDK failures

This commit is contained in:
lingniu
2026-07-16 01:09:56 +08:00
parent e80e1dcea9
commit b93e165590
2 changed files with 76 additions and 15 deletions

View File

@@ -1,5 +1,18 @@
import { expect, test } from 'vitest';
import { buildAMapMarkerURL, gcj02ToWgs84, isValidAMapCoordinate, wgs84ToGcj02 } from './amap';
import { afterEach, expect, test, vi } from 'vitest';
import {
buildAMapMarkerURL,
gcj02ToWgs84,
isValidAMapCoordinate,
loadAMap,
type AMapLike,
wgs84ToGcj02
} from './amap';
afterEach(() => {
delete window.__LINGNIU_APP_CONFIG__;
delete window.AMapLoader;
document.querySelector('script[src="https://webapi.amap.com/loader.js"]')?.remove();
});
test('accepts coordinates inside the China vehicle service range', () => {
expect(isValidAMapCoordinate(116.397128, 39.916527)).toBe(true);
@@ -30,3 +43,31 @@ test('keeps coordinates outside China unchanged and converts AMap marker links a
expect(position[0]).toBeCloseTo(116.40337, 5);
expect(position[1]).toBeCloseTo(39.91793, 5);
});
test('retries the SDK load after a transient AMap loader rejection', async () => {
const amap = {} as AMapLike;
const load = vi.fn()
.mockRejectedValueOnce(new Error('temporary network failure'))
.mockResolvedValueOnce(amap);
window.__LINGNIU_APP_CONFIG__ = { amapWebJsKey: 'amap-web-key' };
window.AMapLoader = { load };
await expect(loadAMap(['AMap.Geocoder'])).rejects.toThrow('temporary network failure');
await expect(loadAMap(['AMap.Geocoder'])).resolves.toBe(amap);
expect(load).toHaveBeenCalledTimes(2);
});
test('removes a failed loader script so a later request can recover', async () => {
const amap = {} as AMapLike;
window.__LINGNIU_APP_CONFIG__ = { amapWebJsKey: 'amap-web-key' };
const firstAttempt = loadAMap(['AMap.ToolBar']);
const failedScript = document.querySelector<HTMLScriptElement>('script[src="https://webapi.amap.com/loader.js"]');
expect(failedScript).not.toBeNull();
failedScript?.dispatchEvent(new Event('error'));
await expect(firstAttempt).rejects.toThrow('高德地图 Loader 加载失败');
expect(document.querySelector('script[src="https://webapi.amap.com/loader.js"]')).toBeNull();
window.AMapLoader = { load: vi.fn().mockResolvedValue(amap) };
await expect(loadAMap(['AMap.ToolBar'])).resolves.toBe(amap);
});

View File

@@ -104,16 +104,31 @@ function loadScript(src: string) {
}
const existing = document.querySelector<HTMLScriptElement>(`script[src="${src}"]`);
if (existing) {
if (existing.dataset.loadState === 'loaded') {
existing.remove();
loadScript(src).then(resolve, reject);
return;
}
existing.addEventListener('load', () => resolve(), { once: true });
existing.addEventListener('error', () => reject(new Error('高德地图 Loader 加载失败')), { once: true });
existing.addEventListener('error', () => {
existing.remove();
reject(new Error('高德地图 Loader 加载失败'));
}, { once: true });
if (window.AMapLoader) resolve();
return;
}
const script = document.createElement('script');
script.src = src;
script.async = true;
script.onload = () => resolve();
script.onerror = () => reject(new Error('高德地图 Loader 加载失败'));
script.dataset.loadState = 'loading';
script.onload = () => {
script.dataset.loadState = 'loaded';
resolve();
};
script.onerror = () => {
script.remove();
reject(new Error('高德地图 Loader 加载失败'));
};
document.head.appendChild(script);
});
}
@@ -221,16 +236,21 @@ export function loadAMap(plugins: AMapPlugin[] = ['AMap.Scale']) {
} else if (config.securityJsCode) {
window._AMapSecurityConfig = { securityJsCode: config.securityJsCode };
}
const promise = loadScript('https://webapi.amap.com/loader.js').then(() => {
if (!window.AMapLoader) {
throw new Error('高德地图 Loader 不可用');
}
return window.AMapLoader.load({
key: config.webJsKey,
version: '2.0',
plugins
}) as Promise<AMapLike>;
});
const promise = loadScript('https://webapi.amap.com/loader.js')
.then(() => {
if (!window.AMapLoader) {
throw new Error('高德地图 Loader 不可用');
}
return window.AMapLoader.load({
key: config.webJsKey,
version: '2.0',
plugins
}) as Promise<AMapLike>;
})
.catch((error: unknown) => {
amapLoaderPromises.delete(key);
throw error;
});
amapLoaderPromises.set(key, promise);
return promise;
}