zhangdizhangdi

Promsie

学习文档

Promise原理/手写

输出类题目

代码实现

普通功能

js
const p = () =>
  new Promise((resolve, reject) => {
    const random = Math.random() * 1000
    setTimeout(() => {
      if (random > 200) {
        reject('p 失败')
      } else {
        resolve('p 成功')
      }
    }, random)
  })

//重试
function withRetry(max = 3) {
  return new Promise((resolve, reject) => {
    let count = 1
    const excute = async () => {
      try {
        const res = await p()
        console.log(res)
        resolve(res)
      } catch (error) {
        console.log(error)
        if (count === max) {
          reject(error)
        } else {
          count++
          excute()
        }
      }
    }
    excute()
  })
}
;(async () => {
  try {
    const res = await withRetry()
    console.log('retry result', res)
  } catch (e) {
    console.log('retry error', e)
  }
})()

//超时
function withTimeout(t = 30000) {
  const timout = () => {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        reject('超时了')
      }, t)
    })
  }

  return Promise.race([timout(), p()])
    .then(res => Promise.resolve(res))
    .catch(e => Promise.reject(e))
}
;(async () => {
  try {
    const res = await withTimeout()
    console.log('timout result', res)
  } catch (error) {
    console.log('timout error ', error)
  }
})()

//取消
function withCancel(p, cancle) {
  return Promise.race([p(), cancle()])
    .then(r => Promise.resolve(r))
    .catch(e => Promise.reject(e))
}

;(async () => {
  const cancel = () =>
    new Promise((resolve, reject) => {
      reject('取消')
    })
  try {
    const res = await withCancel(p, cancel)
    console.log('cancel result', res)
  } catch (e) {
    console.log('cancel error ', e)
  }
})()

限制并发

题目
  1. 实现一个批量请求函数, 能够限制并发量?
  2. 使用 Promise 实现一个异步流量控制的函数, 比如一共 10 个请求, 每个请求的快慢不同, 最多同时 3 个请求发出, 快的一个请求返回后, 就从剩下的 7 个请求里再找一个放进请求池里, 如此循环。
  3. 好多请求, 耗时不同, 按照顺序输出, 尽可能保证快, 写一个函数。
js
const promiseList = [
  new Promise(resolve => {
    setTimeout(resolve, 1000)
  }),
  new Promise(resolve => {
    setTimeout(resolve, 1000)
  }),
  new Promise(resolve => {
    setTimeout(resolve, 1000)
  }),
]
fn(promiseList)

all、allSettled

js
//!!!注意这里与下面的区别,这里立即执行了,有没有 all,allSettled都会执行
let p1 = new Promise(resolve => {
  setTimeout(() => {
    console.log(1000)
    resolve(1000)
  }, 1000)
})
let p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log(2000)
    // resolve(2000)
    reject('error')
  }, 1000)
})
let p3 = new Promise(resolve => {
  setTimeout(() => {
    console.log(3000)
    resolve(3000)
  }, 2000)
})
let p4 = new Promise(resolve => {
  setTimeout(() => {
    console.log(4000)
    resolve(4000)
  }, 1000)
})
let p5 = new Promise(resolve => {
  setTimeout(() => {
    console.log(5000)
    resolve(5000)
  }, 1000)
})

const ps = [p1, p2, p3, p4, p5]
Promise.allSettled(ps)
  .then(r => console.log('allSettled then', r))
  .catch(e => console.log('allSettled error', e))

Promise.all(ps.slice(2))
  .then(r => console.log('all 3个 then', r))
  .catch(e => console.log('all 3个 error', e))
//all 碰到一个error,立即返回
Promise.all(ps)
  .then(r => console.log('all 5个 then', r))
  .catch(e => console.log('all 5个 error', e))

limit

js
const ps2 = []
for (let i = 0; i < 5; i++) {
  const randomTime = Math.random() * 1000
  //!!!注意这里与上面的区别,这里没有立即执行,是一个函数
  const promise = () =>
    new Promise(resolve => {
      setTimeout(() => {
        const msg = (i + 1) * 1000
        console.log('limist promise', msg, randomTime)
        resolve(msg)
      }, randomTime)
    })

  ps2.push(promise)
}

function limitPromise(promiseList, max) {
  const len = promiseList.length
  const res = new Array(len).fill(false)
  let count = 0
  return new Promise(resolve => {
    const excute = i => {
      promiseList[i]()
        .then(r => (res[i] = r))
        .catch(e => (res[i] = e))
        .finally(() => {
          if (count < len) {
            excute(count++)
          } else if (!res.includes(false)) {
            resolve(res)
          }
        })
    }
    while (count < max) {
      excute(count++)
    }
  })
}

limitPromise(ps2, 2)
  .then(res => {
    console.log('limitLoad then', res)
  })
  .catch(err => {
    console.error('limitLoad catch', err)
  })

图片加载

题目
  • 使用 Promise 实现:限制异步操作的并发个数,并尽可能快的完成全部。
  • 有多个图片资源的 url,已经存储在数组 urls 中,而且已经有一个函数 function loadImg,输入一个 url 链接,返回一个 Promise,该 Promise 在图片下载完成的时候 resolve,下载失败则 reject。
  • 但有一个要求,任何时刻同时下载的链接数量不可以超过 3 个。
  • 请写一段代码实现这个需求,要求尽可能快速地将所有图片下载完成。
js
const urls = [
  'https://t7.baidu.com/it/u=1819248061,230866778&fm=193&f=GIF',
  'https://t7.baidu.com/it/u=1297102096,3476971300&fm=193&f=GIF',
  'https://t7.baidu.com/it/u=737555197,308540855&fm=193&f=GIF',
  'https://t7.baidu.com/it/u=3195384123,421318755&fm=193&f=GIF222',
  'https://t7.baidu.com/it/u=3078295664,4026667001&fm=193&f=GIF',
  'https://t7.baidu.com/it/u=2790759587,1933417440&fm=193&f=GIF',
]

function loadImg(url) {
  return new Promise((resolve, reject) => {
    const randomTime = Math.random() * 1000
    const img = new Image()
    img.onload = function () {
      setTimeout(() => {
        console.log('一张图片加载完成 ' + url)
        resolve(img)
      }, randomTime)
    }
    img.onerror = function () {
      reject(new Error('Could not load image at' + url))
    }
    img.src = url
  })
}

loadImages(urls).then(res => {
  console.log('加载图片结束 then', res)
})

function loadImages(urls, limit = 3) {
  const len = urls.length
  const res = new Array(len).fill(false)
  let count = 0
  return new Promise(resolve => {
    const excute = i => {
      loadImg(urls[i])
        .then(r => (res[i] = r))
        .catch(e => (res[i] = e))
        .finally(() => {
          if (count < len) {
            excute(count++)
          } else if (!res.includes(false)) {
            resolve(res)
          }
        })
    }

    while (count < limit) {
      excute(count++)
    }
  })
}

Sleep函数

js
function sleep(delay) {
  return new Promise(resolve => {
    setTimeout(resolve, delay)
  })
}

;(async function () {
  console.log('1')
  await sleep(1000)
  console.log('2')
  await sleep(2000)
  console.log('3')
  await sleep(3000)
})()

async function run() {
  console.log('run 1')
  await sleep(1000)
  console.log('run 2')
  await sleep(2000)
  console.log('run 3')
  await sleep(3000)
}
run()

console.log('11')
sleep(1000).then(() => {
  console.log('22')
  sleep(2000).then(() => {
    console.log('33')
  })
})

间隔输出

js
function createRepeat(fn, repeat, interval) {
  function sleep() {
    return new Promise(resolve => {
      setTimeout(resolve, interval * 1000)
    })
  }

  return async function (...args) {
    fn.apply(this, args)
    for (let i = 1; i < repeat; i++) {
      await sleep()
      fn.apply(this, args)
    }
  }
}

const fn = createRepeat(console.log, 3, 4)
fn('helloWorld') // 每4秒输出一次helloWorld, 输出3次

链式调用

js
const boy = new PlayBoy('Tom')
boy.sayHi().sleep(1000).play('王者').sleep(2000).play('跳一跳')
// 输出
// 大家好我是Tom
// 1s 之后
// 我在玩王者
// 2s 之后
// 我在玩跳一跳
js
class PlayBoy {
  name = ''
  queue = []
  constructor(name) {
    this.name = name
    setTimeout(() => {
      this.next()
    }, 0)
    return this
  }
  next() {
    const fn = this.queue.shift()
    fn && fn()
  }
  sleep(time) {
    const fn = async () => {
      await new Promise(resolve => {
        setTimeout(resolve, time)
      })
      this.next()
    }
    // const fn = () => {
    //     setTimeout(() => {
    //         this.next()
    //     }, time)
    // }
    this.queue.push(fn)
    return this
  }
  sayHi() {
    const fn = () => {
      console.log(`hi,我是${this.name}`)
      this.next()
    }
    this.queue.push(fn)
    return this
  }
  play(str) {
    const fn = () => {
      console.log(str)
      this.next()
    }
    this.queue.push(fn)
    return this
  }
}
const boy = new PlayBoy('Tom')
boy.sayHi().sleep(1000).play('王者').sleep(2000).play('跳一跳')

红绿灯

使用睡眠函数实现红绿灯代码,红灯 2 秒,黄灯 1 秒,绿灯 3 秒,循环改变颜色。

ts
type Lights = { color: string; time: number }[]
class TrafficLight {
  timer
  currentIndex = 0
  lights = [
    { color: 'red', duration: 2000 },
    { color: 'yellow', duration: 1000 },
    { color: 'green', duration: 3000 },
  ]

  constructor() {
    this.changLight()
  }
  changLight() {
    const { color, duration } = this.lights[this.currentIndex]
    console.log('demo1', color)
    this.timer = setTimeout(() => {
      this.currentIndex++
      if (this.currentIndex === this.lights.length) {
        this.currentIndex = 0
      }
      this.changLight()
    }, duration)
  }
  stop() {
    clearTimeout(this.timer)
  }
}
const lights = new TrafficLight()

//取消
onBeforeUnmount(() => {
  lights.stop()
})
ts
function lightOn(delay: number) {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve('')
    }, delay)
  })
}

async function trafficLight(lights: Lights) {
  // eslint-disable-next-line no-constant-condition
  while (true) {
    for (let i = 0; i < lights.length; i++) {
      const { color, time } = lights[i]
      console.log('demo2', color)
      await lightOn(time)
    }
  }
}

trafficLight([
  { color: 'red', time: 1000 },
  { color: 'green', time: 2000 },
  { color: 'yellow', time: 3000 },
])

实现一个信号灯(交通灯)控制器,要求:

  1. 默认情况下,

    • 红灯亮 20 秒,并且最后 5 秒闪烁
    • 绿灯亮 20 秒,并且最后 5 秒闪烁
    • 黄灯亮 10 秒
    • 次序为 红-绿-黄-红-绿-黄
  2. 灯的个数、颜色、持续时间、闪烁时间、灯光次序都可配置,如:
    lights=[{color: ‘#fff’, duration: 10000, twinkleDuration: 5000}, … ]

ts
class TrafficLightWithTwinkle {
  currentIndex = 0
  lights = [
    { color: 'red', duration: 20, twinkleDuration: 5 },
    { color: 'green', duration: 20, twinkleDuration: 5 },
    { color: 'yellow', duration: 10, twinkleDuration: 0 },
  ]

  constructor() {
    this.changeLight()
  }
  wait(duration: number) {
    return new Promise(resolve => {
      setTimeout(resolve, duration * 1000)
    })
  }
  async changeLight() {
    const { color, duration, twinkleDuration } = this.lights[this.currentIndex]
    console.log(`${color}${duration}秒`)
    await this.wait(duration)
    if (twinkleDuration > 0) {
      console.log(`${color} 闪烁 ${twinkleDuration}秒`)
      await this.wait(twinkleDuration)
    }
    this.currentIndex++
    if (this.currentIndex === 3) {
      this.currentIndex = 0
    }
    this.changeLight()
  }
}

const tr = new TrafficLightWithTwinkle()

todo

取消