跳至主要內容

TypeScript 代码片段们

Chilfish大约 1 分钟

Image ArrayBuffer to URL

export function toURL(arrayBuffer: ArrayBuffer) {
  if (!(arrayBuffer instanceof ArrayBuffer)) {
    throw new TypeError(
      'Invalid parameter: arrayBuffer must be an instance of ArrayBuffer',
    )
  }

  let type = 'image/png'
  const buffer = new Uint8Array(arrayBuffer)

  if (buffer.byteLength > 0 && buffer[0] === 60)
    type = 'image/svg+xml'

  else if (
    buffer.byteLength > 3
    && buffer[0] === 71
    && buffer[1] === 73
    && buffer[2] === 70
  )
    type = 'image/gif'

  else if (buffer.byteLength > 2 && buffer[0] === 255 && buffer[1] === 216)
    type = 'image/jpeg'

  const url = URL.createObjectURL(new Blob([buffer], { type }))

  // 在适当的时机手动释放URL对象
  const cleanup = () => {
    URL.revokeObjectURL(url)
  }

  // 返回URL,并在外部调用cleanup函数来释放资源
  return { url, cleanup }
}

Get Image Size

export function getImageSize(url: string) {
  return new Promise<{ width: number, height: number }>((resolve, reject) => {
    const img = new Image()
    img.onload = () => {
      resolve({ width: img.width, height: img.height })
    }
    img.onerror = reject
    img.src = url
  })
}

Cross Window Message

Send message to another window

export function sendAnotherWindowMessage(
  target: Window,
  origin: string,
  message: any,
  transfer?: Transferable[],
) {
  target.postMessage(message, origin, transfer)
}

Receive message from another window

export function getAnotherWindowMessage(
  target: Window,
  origin: string,
  timeout = 5000,
  errorHandler?: (error: Error) => void,
  eventType = 'message',
) {
  return new Promise<MessageEvent>((resolve, reject) => {
    const timer = setTimeout(() => {
      const error = new Error('timeout')
      if (errorHandler)
        errorHandler(error)

      reject(error)
    }, timeout)

    const handler = (event: MessageEvent) => {
      if (event.origin === origin) {
        clearTimeout(timer)
        target.removeEventListener(eventType, handler)
        resolve(event)
      }
    }

    target.addEventListener(eventType, handler)
  })
}

Wait for element

function waitForElement(
  selector: string,
  $: (e: string) => Element | null = e => document.querySelector(e),
): Promise<Element> {
  return new Promise((resolve) => {
    const element = $(selector)
    if (element) {
      resolve(element)
      return
    }

    const observer = new MutationObserver(() => {
      const element = $(selector)
      if (element) {
        resolve(element)
        observer.disconnect()
      }
    })

    observer.observe(document.body, {
      childList: true,
      subtree: true,
    })
  })
}

Promise

超时 Promise

export function timeoutPromise<T>(
  promise: Promise<T>,
  timeout: number,
  message?: string,
) {
  return Promise.race([
    promise,
    new Promise<T>((resolve, reject) => {
      setTimeout(() => {
        reject(new Error(message || 'timeout'))
      }, timeout)
    }),
  ])
}

错误重试

export function retryPromise<T>(
  promise: () => Promise<T>,
  retryCount = 5,
  interval = 1000,
) {
  return new Promise<T>((resolve, reject) => {
    let count = 0
    const run = () => {
      promise()
        .then(resolve)
        .catch((error) => {
          if (count < retryCount) {
            count += 1
            setTimeout(run, interval)
          }
          else {
            reject(error)
          }
        })
    }
    run()
  })
}

可中断的

export function interruptablePromise<T>(
  promise: Promise<T>,
  abortSignal: AbortSignal,
) {
  return new Promise<T>((resolve, reject) => {
    abortSignal.addEventListener('abort', () => {
      reject(new Error('abort'))
    })

    promise
      .then(resolve)
      .catch(reject)
  })
}

// 使用
const abortController = new AbortController()
const promise = interruptablePromise(fetch('https://example.com'), abortController.signal)

// 手动中断
abortController.abort()

其实 fetch 本身可以将 signal 作为第二个参数传入,来实现中断