export default function jsonp()

in packages-fetcher/fetcher-jsonp/src/util/jsonp.ts [18:119]


export default function jsonp<T = void>(url = '', options: IJsonpOptions = {}): Promise<IJsonpResponse<T>> {
  const {
    timeout = 5000,
    charset,
    jsonpCallback = 'callback',
    jsonpCallbackFunction = generateCallbackName(),
    signal
  } = options;
  const scriptElement = document.createElement('script');
  let timeoutId: number | undefined;
  
  scriptElement.src = `${url}${url.indexOf('?') < 0 ? '?' : '&'}${jsonpCallback}=${jsonpCallbackFunction}`;
  
  if (charset) {
    scriptElement.setAttribute('charset', charset);
  }
  
  (document.head || document.getElementsByTagName('head')[0]).appendChild(scriptElement);
  
  // 清除全局污染
  function cleanup(): void {
    clearCallbackFn(jsonpCallbackFunction);
    
    // 清除 script DOM
    if (scriptElement.parentNode) {
      scriptElement.parentNode.removeChild(scriptElement);
    }
    
    // 清除 timeout
    if (timeoutId) {
      window.clearTimeout(timeoutId);
      timeoutId = undefined;
    }
  }
  
  /**
   * 当 abort 或 timeout 的时候,执行 cleanup,虽然 DOM 被移除了,但 JS 的加载还在继续,超时后还有可能加载完毕,
   * 需要避免这种情况下抛错
   */
  function cleanupPrematurely(): void {
    cleanup();
    
    (window as unknown as Record<string, () => void>)[jsonpCallbackFunction] = () => clearCallbackFn(jsonpCallbackFunction);
  }
  
  return new Promise<IJsonpResponse<T>>((resolve, reject) => {
    let returned = false;
    
    // 异步事件 1:回调
    (window as unknown as Record<string, (result: T) => void>)[jsonpCallbackFunction] = (result: T) => {
      if (returned) {
        return;
      }
      
      returned = true;
      resolve({
        ok: true,
        url,
        json: (): Promise<T> => Promise.resolve(result)
      });
      cleanup();
    };
    
    // 异步事件 2:超时
    if (timeout > 0) {
      timeoutId = window.setTimeout(() => {
        if (returned) {
          return;
        }
  
        returned = true;
        reject(createError(EJsonpError.TIMEOUT, `JSONP timeout after ${timeout}ms, url = ${url}`));
        cleanupPrematurely();
      }, timeout);
    }
  
    // 异步事件 3:abort,监听 signal 的 abort(和 fetch 一样)
    // https://developer.mozilla.org/en-US/docs/Web/API/AbortController
    if (signal) {
      signal.addEventListener('abort', () => {
        if (returned) {
          return;
        }
  
        returned = true;
        reject(createError('AbortError', `The JSONP was aborted, url = ${url}`));
        cleanupPrematurely();
      });
    }
    
    // 异步事件 3:404/500 等
    scriptElement.onerror = () => {
      if (returned) {
        return;
      }
      
      returned = true;
      reject(createError(EJsonpError.NETWORK, `JSONP failed, url = ${url}`));
      cleanup();
    };
  });
}