Skip to content
📖预计阅读时长:0 分钟字数:0

1. Promise完整实现

推导链

Q1:Promise是什么? → 状态机:pending → fulfilled / rejected,不可逆 → 需要:state、value、callback[]

Q2:resolve / reject 在干嘛? → 改状态 + 存值 + 执行所有等待的回调 → 为什么要callbacks?因为then可能在pending时调用

Q3:then为什么能链式调用? → 返回新Promise → 新Promise的resolve / reject 由上一个then的返回值决定

Q4:then的返回值怎么处理? → 普通值:直接resolve(rusult) → Promise:result.then(resolve, reject) 等它完成

Q5: 为什么要 setTimeout?
→ 规范要求 then 回调异步执行

Q6: 透传是什么?
→ .then().then(v => ...) 值要能传下去
→ 默认 v => v 和 e =>

代码

javascript
const PENDING = 'pending', FULFILLED = 'fulfilled', REJECTED = 'rejected'

class MyPromise {
  constructor(executor) {
    this.state = PENDING
    this.value = undefined
    this.callbacks = []

    const resolve = (value) => {
      if (this.state !== PENDING) return
      this.state = FULFILLED
      this.value = value
      this.callbacks.forEach(cb => cb.onFulfilled(value))
    }

    const reject = (reason) => {
      if (this.state !== PENDING) return
      this.state = REJECTED
      this.value = reason
      this.callbacks.forEach(cb => cb.onRejected(reason))
    }

    try { executor(resolve, reject) } catch (e) { reject(e) }
  }

  then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v
    onRejected = typeof onRejected === 'function' ? onRejected : e => { throw e }

    return new MyPromise((resolve, reject) => {
      const handle = (callback) => {
        try {
          const result = callback(this.value)
          result instanceof MyPromise ? result.then(resolve, reject) : resolve(result)
        } catch (e) { reject(e) }
      }

      if (this.state === FULFILLED) return setTimeout(() => handle(onFulfilled))
      if (this.state === REJECTED) return setTimeout(() => handle(onRejected))
      
      this.callbacks.push({
        onFulfilled: () => setTimeout(() => handle(onFulfilled)),
        onRejected: () => setTimeout(() => handle(onRejected))
      })
    })
  }

  catch(onRejected) { return this.then(null, onRejected) }
  static resolve(v) { return v instanceof MyPromise ? v : new MyPromise(r => r(v)) }
  static reject(e) { return new MyPromise((_, r) => r(e)) }
}

// 测试
new MyPromise(r => setTimeout(() => r(1), 100))
  .then(v => { console.log(v); return v + 1 })
  .then(v => new MyPromise(r => setTimeout(() => r(v + 1), 100)))
  .then(console.log)  // 1, 3

2. Promise.all

推导链

Q1: all 干嘛?→ 全部成功才成功,一个失败就失败
Q2: 怎么知道全部完成?→ 计数器 finished === total
Q3: 结果顺序?→ results[index] 按索引存

代码

javascript
Promise.myAll = function(promises) {
  return new Promise((resolve, reject) => {
    const results = []
    let finished = 0
    const arr = Array.from(promises)
    
    if (arr.length === 0) return resolve([])
    
    arr.forEach((p, i) => {
      Promise.resolve(p).then(
        value => {
          results[i] = value
          if (++finished === arr.length) resolve(results)
        },
        reject  // 一个失败就整体失败
      )
    })
  })
}

// 测试
Promise.myAll([Promise.resolve(1), Promise.resolve(2), 3]).then(console.log) // [1,2,3]
Promise.myAll([Promise.resolve(1), Promise.reject('err')]).catch(console.log) // 'err'

3. Promise.race

推导链

Q1: race 干嘛?→ 谁先完成用谁(无论成功失败)
Q2: 怎么实现?→ 每个都 .then(resolve, reject),第一个触发后状态锁定

代码

javascript
Promise.myRace = function(promises) {
  return new Promise((resolve, reject) => {
    for (const p of promises) {
      Promise.resolve(p).then(resolve, reject)
    }
  })
}

// 测试
Promise.myRace([
  new Promise(r => setTimeout(() => r('slow'), 200)),
  new Promise(r => setTimeout(() => r('fast'), 100))
]).then(console.log) // 'fast'

4. Promise.allSettled

推导链

Q1: 和 all 区别?→ 不管成功失败,等全部完成
Q2: 返回格式?→

代码

javascript
Promise.myAllSettled = function(promises) {
  return new Promise(resolve => {
    const results = []
    let finished = 0
    const arr = Array.from(promises)
    
    if (arr.length === 0) return resolve([])
    
    arr.forEach((p, i) => {
      Promise.resolve(p).then(
        value => { results[i] = { status: 'fulfilled', value } },
        reason => { results[i] = { status: 'rejected', reason } }
      ).finally(() => {
        if (++finished === arr.length) resolve(results)
      })
    })
  })
}

// 测试
Promise.myAllSettled([Promise.resolve(1), Promise.reject('err')])
  .then(console.log) // [{status:'fulfilled',value:1}, {status:'rejected',reason:'err'}]

5. Promise.any

推导链

Q1: 和 race 区别?→ 第一个成功的,全失败才失败
Q2: 全失败返回什么?→ AggregateError

代码

javascript
Promise.myAny = function(promises) {
  return new Promise((resolve, reject) => {
    const errors = []
    let rejected = 0
    const arr = Array.from(promises)
    
    if (arr.length === 0) return reject(new AggregateError([], 'All promises were rejected'))
    
    arr.forEach((p, i) => {
      Promise.resolve(p).then(
        resolve,  // 第一个成功就 resolve
        reason => {
          errors[i] = reason
          if (++rejected === arr.length) {
            reject(new AggregateError(errors, 'All promises were rejected'))
          }
        }
      )
    })
  })
}

// 测试
Promise.myAny([Promise.reject(1), Promise.resolve(2)]).then(console.log) // 2
Promise.myAny([Promise.reject(1), Promise.reject(2)]).catch(e => console.log(e.errors)) // [1,2]

6. 并发控制

推导链

Q1: 什么场景?→ 10个请求,同时最多跑3个
Q2: 怎么控制?→ 维护 executing 池,满了就等
Q3: 怎么等?→ Promise.race 等最快完成的
Q4: 完成后?→ 从池子删掉,继续下一个

变体速查

形态核心
async 版await Promise.race(executing)
new Promise 版next() 递归驱动

代码

javascript
// 写法一:async + Promise.race
async function concurrent(tasks, limit) {
  const res = [], executing = []

  for (let i=0; i<tasks.length; i++) {
    const p = tasks[i]().then(
        r => {
            res[i] = r
            executing.splice(i)
        }
    )
    executing.push(p)
    if (executing.length >= limit) {
        await Promise.race(executing)
    }
  }

  Promise.all(executing)
  return res
}

// 写法二:new Promise + next 递归
function concurrentV2(tasks, limit) {
    return new Promise((resolve, reject) => {
        let index = 0 ,finished = 0
        const res = []
        const len = tasks.length
        function next() {
            if (finished === len) resolve(res)
            if (index === len) return
            const i = index++
            tasks[i]()
            .then(v => {
                res[i] = v
            })
            .catch(reject)
            .finnaly( () => {
                finished++
                next() 
            })
        }

        for (let i=0; i < Math.min(limit, len); i++) next()
    })
}

// 写法3, p-limit
function pLimit(concurrency) {
  let activeCount = 0
  const q = []

  function next() {
    if (q.length && activeCount < concurrency) {
        const {fn, resolve, reject} = q.shift()
        fn().then(resolve).catch(reject).finally(() => {activeCount--; next()})
    }
  }

  return fn => new Promise((resolve, reject) => {q.push({fn, resolve, reject}); next()})
}

// 测试
// 测试任务:模拟异步请求,打印开始/结束时间
const createTasks = () => [1, 2, 3, 4, 5].map(n => () => 
  new Promise(resolve => {
    console.log(`任务${n}开始 - ${Date.now() % 10000}ms`)
    setTimeout(() => {
      console.log(`任务${n}完成 - ${Date.now() % 10000}ms`)
      resolve(n * 10)
    }, 100)
  })
)

// 测试1:async + Promise.race
console.log('--- 测试 concurrent ---')
concurrent(createTasks(), 2).then(res => {
  console.log('结果:', res)  // [10, 20, 30, 40, 50]
})

// 测试2:new Promise + next 递归
setTimeout(() => {
  console.log('\n--- 测试 concurrentV2 ---')
  concurrentV2(createTasks(), 2).then(res => {
    console.log('结果:', res)  // [10, 20, 30, 40, 50]
  })
}, 1000)

// 测试3:p-limit
setTimeout(() => {
  console.log('\n--- 测试 pLimit ---')
  const limit = pLimit(2)
  const tasks = [1, 2, 3, 4, 5].map(n => 
    limit(() => new Promise(resolve => {
      console.log(`任务${n}开始 - ${Date.now() % 10000}ms`)
      setTimeout(() => {
        console.log(`任务${n}完成 - ${Date.now() % 10000}ms`)
        resolve(n * 10)
      }, 100)
    }))
  )
  Promise.all(tasks).then(res => {
    console.log('结果:', res)  // [10, 20, 30, 40, 50]
  })
}, 2000)

7. retry重试 + 超时控制

推导链

Q1: retry 怎么实现?→ 失败就递归,次数减一
Q2: 超时怎么控制?→ Promise.race([请求, 超时Promise])
Q3: 带延迟?→ 失败后 setTimeout 再重试

代码

javascript
// 基础 retry
function retry(fn, times) {
  return fn().catch(err => {
    if (times <= 0) throw err
    return retry(fn, times - 1)
  })
}

// 带延迟的 retry
function retryWithBackoff(fn, maxTimes, baseDelay = 1000, attempt = 0) {
  return fn().catch(err => {
    if (attempt >= maxTimes) throw err
    const delay = baseDelay * (2 ** attempt)  // 1s, 2s, 4s, 8s...
    return new Promise(r => setTimeout(r, delay))
      .then(() => retryWithBackoff(fn, maxTimes, baseDelay, attempt + 1))
  })
}

// 超时控制
function withTimeout(fn, timeout) {
  return Promise.race([
    fn(),
    new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), timeout))
  ])
}

// 组合:retry + timeout
function retryWithTimeout(fn, times, timeout) {
  return retry(() => withTimeout(fn, timeout), times)
}

// 测试
let count = 0
const unstable = () => new Promise((res, rej) => ++count < 3 ? rej('fail') : res('ok'))
retry(unstable, 3).then(console.log) // 'ok'(第3次成功)

8. sleep

推导链

Q1: sleep 干嘛?→ 等待指定时间
Q2: 怎么实现?→ Promise + setTimeout

代码

javascript
const sleep = (ms) => new Promise(r => setTimeout(r, ms))

// 同步阻塞版(不推荐,会卡主线程)
function sleepSync(ms) {
  const start = Date.now()
  while (Date.now() - start < ms) {}
}

// 测试
async function test() {
  console.log('start')
  await sleep(1000)
  console.log('1s later')
}
test()

9. 红绿灯循环

推导链

Q1: 需求?→ 红3s → 绿2s → 黄1s → 循环
Q2: 怎么串行?→ async/await 或 Promise 链
Q3: 怎么循环?→ 递归或 while(true)

代码

javascript
const sleep = (ms) => new Promise(r => setTimeout(r, ms))

async function trafficLight() {
  while (true) {
    console.log('🔴 红灯')
    await sleep(3000)
    console.log('🟢 绿灯')
    await sleep(2000)
    console.log('🟡 黄灯')
    await sleep(1000)
  }
}

// Promise 链版
function trafficLightV2() {
  const light = (color, ms) => () => {
    console.log(color)
    return sleep(ms)
  }
  
  Promise.resolve()
    .then(light('🔴 红灯', 3000))
    .then(light('🟢 绿灯', 2000))
    .then(light('🟡 黄灯', 1000))
    .then(trafficLightV2)  // 递归循环
}

trafficLight()

10. Promisify

推导链

Q1: 干嘛用?→ 把 callback 风格转成 Promise 风格
Q2: callback 格式?→ fn(args..., (err, result) => {})
Q3: 怎么转?→ 返回新函数,内部 new Promise 包装

代码

javascript
function promisify(fn) {
    return (...args) => new Promise((resolve, reject) => [
        fn(...args, (e, r) => {
            if (e !== null) reject(e)
            else resolve(r) 
        })
    ])
}

// 模拟测试
const callbackFn = (a, b, cb) => setTimeout(() => cb(null, a + b), 100)
const asyncFn = promisify(callbackFn)
asyncFn(1, 2).then(console.log) // 3

11. 发布订阅 + 观察者模式

推导链

Q1: 发布订阅是什么?
→ 事件中心:on 订阅、emit 发布、off 取消
→ 发布者和订阅者不直接通信,通过事件中心

Q2: 观察者模式区别?
→ 观察者直接订阅目标,目标直接通知观察者
→ 没有中间的事件中心

Q3: 核心数据结构?
→ 发布订阅:{ eventName: [cb1, cb2] }
→ 观察者:observers: [observer1, observer2]

对比

发布订阅观察者
耦合度松耦合(有中间层)紧耦合(直接引用)
通信通过事件中心目标直接调观察者
场景EventEmitter、Vue事件总线MobX、Vue响应式

代码

javascript
// 发布订阅
class EventEmitter {
  constructor() { this.events = {} }
  
  on(event, cb) {
    (this.events[event] ||= []).push(cb)
    return this
  }
  
  emit(event, ...args) {
    (this.events[event] || []).forEach(cb => cb(...args))
    return this
  }
  
  off(event, cb) {
    if (!cb) this.events[event] = []
    else this.events[event] = (this.events[event] || []).filter(f => f !== cb && f.raw !== cb)
    return this
  }
  
  once(event, cb) {
    const wrapper = (...args) => { cb(...args); this.off(event, wrapper) }
    wrapper.raw = cb
    return this.on(event, wrapper)
  }
}

// 观察者模式
class Subject {
  constructor() { this.observers = [] }
  add(observer) { this.observers.push(observer) }
  remove(observer) { this.observers = this.observers.filter(o => o !== observer) }
  notify(data) { this.observers.forEach(o => o.update(data)) }
}

class Observer {
  constructor(name) { this.name = name }
  update(data) { console.log(`${this.name} 收到: ${data}`) }
}

// 测试发布订阅
const bus = new EventEmitter()
bus.on('msg', console.log)
bus.emit('msg', 'hello') // 'hello'

// 测试观察者
const subject = new Subject()
const ob1 = new Observer('A')
const ob2 = new Observer('B')
subject.add(ob1)
subject.add(ob2)
subject.notify('更新了') // A 收到: 更新了, B 收到: 更新了

12. CodingMan

推导链

Q1: 需求?→ 链式调用 + 异步任务队列
Q2: 怎么链式?→ 每个方法 return this
Q3: 怎么等链式调用完再执行?→ setTimeout 宏任务延迟启动
Q4: 怎么串行执行?→ 两种方案:next() 内部驱动 / for await 外部迭代
Q5: sleepFirst 怎么插队?→ unshift 到队列头部

变体速查

形态核心
next() 驱动每个任务执行完调 this.next()
for await 迭代任务返回 Promise,外部 await 遍历

代码

javascript
// 写法一:next() 内部驱动
function CodingMan(name) {
  const queue = []
  
  const next = () => {
    const task = queue.shift()
    task && task()
  }
  
  const ctx = {
    eat(food) {
      queue.push(() => {
        console.log(`Eat ${food}`)
        next()
      })
      return this
    },
    
    sleep(sec) {
      queue.push(() => {
        console.log(`Sleep ${sec}s...`)
        setTimeout(() => { console.log('Wake up'); next() }, sec * 1000)
      })
      return this
    },
    
    sleepFirst(sec) {
      queue.unshift(() => {
        console.log(`Sleep ${sec}s...`)
        setTimeout(() => { console.log('Wake up'); next() }, sec * 1000)
      })
      return this
    }
  }
  
  // 初始任务
  queue.push(() => { console.log(`Hi, I am ${name}`); next() })
  
  // setTimeout 等链式调用完再启动
  setTimeout(() => next())
  
  return ctx
}

// 写法二:for await 外部迭代
function CodingManV2(name) {
  const queue = []
  
  const ctx = {
    eat(food) {
      queue.push(() => console.log(`Eat ${food}`))
      return this
    },
    
    sleep(sec) {
      queue.push(() => new Promise(r => {
        console.log(`Sleep ${sec}s...`)
        setTimeout(() => { console.log('Wake up'); r() }, sec * 1000)
      }))
      return this
    },
    
    sleepFirst(sec) {
      queue.unshift(() => new Promise(r => {
        console.log(`Sleep ${sec}s...`)
        setTimeout(() => { console.log('Wake up'); r() }, sec * 1000)
      }))
      return this
    }
  }
  
  // 初始任务
  queue.push(() => console.log(`Hi, I am ${name}`))
  
  // setTimeout 等链式调用完,然后 for await 遍历执行
  setTimeout(async () => {
    for (const task of queue) await task()
  })
  
  return ctx
}

// 测试
CodingMan('Jack').eat('breakfast').sleep(2).eat('lunch')
// Hi, I am Jack → Eat breakfast → Sleep 2s... → Wake up → Eat lunch

CodingMan('Jack').sleepFirst(2).eat('breakfast')
// Sleep 2s... → Wake up → Hi, I am Jack → Eat breakfast