JS 手写题大汇总 (三)--- 异步

125 阅读1分钟

Promise

Promise
// 1. 设置状态和值
// 2. resolve 和 reject 修改值
// 3. fn 函数的错误处理
// 4. 值穿透
// 5. 任务队列和错误处理
// 6. 两个延迟执行函数队列
// 7. resolvePromise
//    1. 循环爆栈处理
//    2. 普通值和没有 then 方法的值直接 resolve
//    3. 执行函数并递归调用 resolvePromise 注意要绑定 this
const PromisePending = 'PENDING'
const PromiseFulfilled = 'FULFILLED'
const PromiseRejected = 'REJECTED'

class MyPromise {
  constructor(fn) {
    if (typeof fn !== 'function') {
      return new TypeError(`Promise resolver ${fn} is not a function`)
    }
    this.PromiseState = PromisePending
    this.PromiseResult = null

    this.onFulfilledCallbacks = []
    this.onRejectedCallbacks = []

    const resolve = (value) => {
      this.PromiseResult = value
      this.PromiseState = PromiseFulfilled
      this.onFulfilledCallbacks.forEach(fn => fn())
    }

    const reject = (reason) => {
      this.PromiseResult = reason
      this.PromiseState = PromiseRejected
      this.onRejectedCallbacks.forEach(fn => fn())
    }

    try {
      fn(resolve, reject)
    } catch (error) {
      reject(error)
    }
  }

  then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
    onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }
    const promise2 = new MyPromise((resolve, reject) => {
      if (this.PromiseState === PromisePending) {
        this.onFulfilledCallbacks.push(() => {
          process.nextTick(() => {
            try {
              const x = onFulfilled(this.PromiseResult)
              resolvePromise(promise2, x, resolve, reject)
            } catch (e) {
              reject(e)
            }
          })
        })

        this.onRejectedCallbacks.push(() => {
          process.nextTick(() => {
            try {
              const x = onRejected(this.PromiseResult)
              resolvePromise(promise2, x, resolve, reject)
            } catch (e) {
              reject(e)
            }
          })
        })
      }

      if (this.PromiseState === PromiseFulfilled) {
        process.nextTick(() => {
          try {
            const x = onFulfilled(this.PromiseResult)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        })
      }

      if (this.PromiseState === PromiseRejected) {
        process.nextTick(() => {
          try {
            const x = onRejected(this.PromiseResult)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        })
      }
    })
    return promise2
  }

  catch(onRejected) {
    return this.then(undefined, onRejected)
  }

  finally(callback) {
    return this.then(callback, callback)
  }
}


const resolvePromise = (promise2, x, resolve, reject) => {
  if (promise2 === x)
    return reject(new Error('Chaining cycle detected for promise #<Promise>"'))

  if (x && (typeof x === 'object' || typeof x === 'function')) {
    let called = false
    try {
      const then = x.then
      if (typeof then !== 'function') {
        resolve(x)
      } else {
        then.call(x,
          value => {
            if (called) return
            called = true
            resolvePromise(promise2, value, resolve, reject)
          },
          reason => {
            if (called) return
            called = true
            reject(reason)
          }
        )
      }
    } catch (e) {
      if (called) return
      called = true
      reject(e)
    }
  } else {
    resolve(x)
  }
}


MyPromise.resolve = (value) => {
  return new MyPromise((resolve, reject) => {
    if (value in MyPromise) {
      return value
    } else if (typeof value === 'object' && 'then' in value) {
      return value.then(resolve, reject)
    } else {
      return new MyPromise(resolve => resolve(value))
    }
  })
}

MyPromise.reject = (reason) => {
  return new MyPromise((resolve, reject) => reject(reason))
}


MyPromise.all = (promises) => {
  return new MyPromise((resolve, reject) => {
    if (!Array.isArray(promises))
      return reject(new TypeError('Arguments is not iterator'))
    let n = promises.length
    if (!n)
      resolve(promises)
    let cnt = 0
    const res = new Array(n)
    promises.forEach((promise, index) => {
      MyPromise.resolve(promise).then(
        value => {
          cnt++
          res[index] = value
          cnt === n && resolve(res)
        },
        reason => {
          reject(reason)
        }
      )
    })
  })
}


MyPromise.race = (promises) => {
  return new MyPromise((resolve, reject) => {
    if (!Array.isArray(promises))
      return reject(new TypeError('Arguments is not iterator'))

    promises.forEach(promise => {
      MyPromise.resolve(promise).then(resolve, reject)
    })
  })
}

MyPromise.allSettled = (promises) => {
  return new MyPromise((resolve, reject) => {
    if (!Array.isArray(promises))
      return reject(new TypeError('Arguments is not iterator'))
    let n = promises.length
    if (!n)
      resolve(promises)
    let cnt = 0
    const res = new Array(n)
    promises.forEach((promise, index) => {
      MyPromise.resolve(promise).then(
        value => {
          cnt++
          res[index] = {
            status: 'fulfilled',
            value
          }
          cnt === n && resolve(res)
        },
        reason => {
          cnt++
          res[ind] = {
            status: 'rejected',
            reason
          }
          cnt === n && resolve(res)
        }
      )
    })
  })
}

MyPromise.any = (promises) => {
  return new MyPromise((resolve, reject) => {
    if (!Array.isArray(promises))
      return reject(new TypeError('Arguments is not iterator'))

    const n = promises.length
    if (!n)
      return reject(new AggregateError('All promises were iterator'))

    let cnt = 0
    const errors = new Array(n)
    promises.forEach((promise, index) => {
      MyPromise.resolve(promise).then(
        value => {
          resolve(value)
        },
        reason => {
          cnt++
          errors[index] = reason
          cnt === n && reject(new AggregateError(errors))
        }
      )
    })
  })
}

并行限制 Promise

1. 类方法
class Scheduler {
  constructor(promises, limit) {
    this.queue = promises
    this.limit = limit
    this.runCount = 0

    this.len = promises.length
    this.result = new Array(this.len)
    this.index = 0
  }

  start() {
    for (let i = 0; i < this.limit; i++) {
      this.request()
    }
  }

  request() {
    if (!this.queue.length || this.runCount >= this.limit) return
    const task = this.queue.shift()
    this.runCount++
    task().then((res) => {
      this.result[this.index++] = res
      if (this.index === this.len) console.log(this.result)
      this.runCount--
      this.request()
    })
  }
}

2. 函数方法
function MyPromise(promises, limit) {
  return new Promise(resolve => {
    let runCount = 0

    const len = promises.length
    const result = new Array(len)
    let index = 0

    function request() {
      if (!promises || !promises.length || runCount >= limit) return
      const task = promises.shift()
      runCount++
      task().then(res => {
        result[index++] = res
        if (index === len) resolve(result)
        runCount--
        request()
      })
    }
    function start() {
      for (let i = 0; i < limit; i++) {
        request()
      }
    }
    start()
  })
}

测试用例
const promise1 = (content, timeout) => {
  return () => {
    return new Promise(res => {
      setTimeout(() => {
        console.log(content)
        console.log(Date.now() - now)
        res(content)
      }, timeout)
    })
  }
}

const promises = [
  promise1(1, 3000),
  promise1(2, 2000),
  promise1(3, 1000),
  promise1(4, 5000),
  promise1(5, 2000),
  promise1(6, 4000),
  promise1(7, 6000),
  promise1(8, 3000),
]
let now = Date.now()
const scheduler = new Scheduler(promises, 3)
scheduler.start()

MyPromise(promises, 3).then(res => console.log(res))

顺序执行 Promise

const promise1 = (content, timeout) => {
  return () => {
    return new Promise(res => {
      setTimeout(() => {
        console.log(content)
        console.log(Date.now() - now)
        res(content)
      }, timeout)
    })
  }
}

const promises = [
  promise1(1, 3000),
  promise1(2, 2000),
  promise1(3, 1000),
  promise1(4, 2000),
  promise1(5, 2000),
  promise1(6, 1000),
  promise1(7, 1000),
  promise1(8, 500),
]
let now = Date.now()

Promise.resolve
function orderPromise(promises) {
  let queue = Promise.resolve()
  const data = []
  promises.forEach(promise => {
    queue = queue.then(promise).then(res => {
      data.push(res)
      return data
    })
  })
  return queue
}

// 用法
orderPromise(promises).then(res => {
  console.log(res)
})

// async await
async function orderPromise1(promises) {
  let index = 0
  const res = new Array(promises.length)
  while (index < promises.length) {
    res[index] = await promises[index]()
    index++
  }
  return res
}
// 用法
(async () => {
  const res = await orderPromise1(promises)
  console.log(res)
})()

递归
function orderPromise2(promises, index = 0) {
  if (index < promises.length) {
    promises[index]().then(() => {
      orderPromise2(promises, index + 1)
    })
  }
}
orderPromise2(promises, 0)

LazyMan

class LazyMan {
  constructor() {
    this.tasks = []
    const task = () => {
      console.log('开始')
      this.next()
    }
    this.tasks.push(task)
    setTimeout(() => {
      this.next()
    })
  }

  next() {
    const task = this.tasks.shift()
    task && task()
  }

  sleep(delay) {
    this.sleepWrapper(delay, false)
    return this
  }

  sleepFirst(delay) {
    this.sleepWrapper(delay, true)
    return this
  }

  sleepWrapper(delay, isFirst) {
    const task = () => {
      console.log('我要睡觉')
      setTimeout(() => {
        this.next()
      }, delay)
    }
    if (isFirst) {
      this.tasks.unshift(task)
    } else {
      this.tasks.push(task)
    }
  }

  addTask(fn) {
    const task = () => {
      fn()
      this.next()
    }
    this.tasks.push(task)
    return this
  }
}

const man = new LazyMan()
const eat = () => console.log('eat')
const run = () => console.log('run')
const study = () => console.log('study')
man.addTask(eat).sleep(2000).addTask(run).sleep(1000).addTask(study).sleepFirst(2000)

AJAX

const xhr = new XMLHttpRequest()

xhr.open('get', '/', true)
xhr.setResponseHeader('Accept', 'application/json')
xhr.responseType = 'json'

xhr.onreadystatechange = function() {
  if (this.readyState === 4) {
    if (this.status === 200) {
      return this.responseText
    } else {
      throw new Error(this.responseText)
    }
  }
}
xhr.send(null)

function promiseAjax(method, url, isAsync = true) {
  return new Promise((res, rej) => {
    const xhr = new XMLHttpRequest()
    xhr.open(method, url, isAsync)
    xhr.setRequestHeader('Content-Type', 'application/json')
    xhr.onreadystatechange = function() {
      if (this.readyState === 4) {
        if (this.status === 200) {
          res(this.responseText)
        } else {
          rej(new Error(this.responeText))
        }
      }
    }
    xhr.open(null)
  })
}

JSONP

function jsonp(url, params, cb) {
  const script = document.createElement('script')
  const query = { ...params, cb }
  const arr = []
  for (const param of query) {
    arr.push(`${param}=${query[param]}`)
  }
  script.src = `${url}?${arr.join('&')}`
  ducument.body.append(script)
  window[cb] = function(data) {
    console.log(data)
    document.body.removeChild(script)
  }
}

红绿灯

function red() { console.log('red') }
function yellow() { console.log('yellow') }
function green() { console.log('green') }

function task(color, timeout, callback) {
  setTimeout(() => {
    if (color === 'red') {
      red()    
    } else if (color === 'yellow') {
      yellow()
    } else {
      green()
    }
    callback()
  }, timeout)
}

// 回调地狱
function loop() {
  task('red', 1000, () => {
    task('yellow', 1000, () => {
      task('red', 1000, loop)
    })
  })
}

function task1(color, timeout) {
  return new Promise(res => {
    setTimeout(() => {
      if (color === 'red') {
        red()    
      } else if (color === 'yellow') {
        yellow()
      } else {
        green()
      }
      res()
    }, timeout)
  })
}

// Promise
const loop1 = () => {
  task1('red', 1000)
    .then(() => task1('yellow', 1000))
    .then(() => task1('green', 1000))
    .then(loop1)
}

const loop2 = async () => {
  await task1('red', 1000)
  await task1('yellow', 1000)
  await task1('green', 1000)
  loop2()
}
loop()

一秒打印一个数字

function print(cnt) {
  setTimeout(() => {
    console.log(cnt)
    print(cnt + 1)
  }, 1000)
}

for (var i = 1; i < 4; i++) {
  setTimeout((j) => {
    console.log(j)
  }, i * 1000, i)
}

for (var i = 1; i < 4; i++) {
  ((j) => {
    setTimeout(() => {
      console.log(j)
    }, i * 1000)
  })(i)
}