2024 Promise 面试手写题

236 阅读8分钟

三种状态:等待中(pending)完成了 (resolved)拒绝了(rejected)

2 个过程:pending -> resolved pending -> rejected

Promise 实现了链式调用,也就是说每次调用 then 之后返回的都是一个 Promise,并且是一个全新的 Promise,原因也是因为状态不可变。

如果在 then 中 使用了 return,那么 return 的值会被 Promise.resolve() 包装

优点:Promise 也很好地解决了回调地狱的问题

缺点:无法取消 Promise,错误需要通过回调函数捕获

手写 Promise

func(params, cb) --> promise(resolve, reject).then(res => {}).catch(err => {}).finally(() => {}) --> async await

观察者模式: 收集依赖 -> 触发通知 -> 取出依赖执行

在Promise里,执行顺序是then收集依赖 -> 异步触发resolve -> resolve执行依赖

//Promise/A+规定的三种状态
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

class MyPromise {
  // 构造方法接收一个回调
  constructor(executor) {
    this._status = PENDING     // Promise状态
    this._value = undefined    // 储存then回调return的值
    this._resolveQueue = []    // 成功队列, resolve时触发
    this._rejectQueue = []     // 失败队列, reject时触发

    // 由于resolve/reject是在executor内部被调用, 因此需要使用箭头函数固定this指向, 否则找不到this._resolveQueue
    let _resolve = (val) => {
      //把resolve执行回调的操作封装成一个函数,放进setTimeout里,以兼容executor是同步代码的情况
      const run = () => {
        if(this._status !== PENDING) return   // 对应规范中的"状态只能由pending到fulfilled或rejected"
        this._status = FULFILLED              // 变更状态
        this._value = val                     // 储存当前value

        // 这里之所以使用一个队列来储存回调,是为了实现规范要求的 "then 方法可以被同一个 promise 调用多次"
        // 如果使用一个变量而非队列来储存回调,那么即使多次p1.then()也只会执行一次回调
        while(this._resolveQueue.length) {    
          const callback = this._resolveQueue.shift()
          callback(val)
        }
      }
      setTimeout(run)
    }
    // 实现同resolve
    let _reject = (val) => {
      const run = () => {
        if(this._status !== PENDING) return   // 对应规范中的"状态只能由pending到fulfilled或rejected"
        this._status = REJECTED               // 变更状态
        this._value = val                     // 储存当前value
        while(this._rejectQueue.length) {
          const callback = this._rejectQueue.shift()
          callback(val)
        }
      }
      setTimeout(run)
    }
    // new Promise()时立即执行executor,并传入resolve和reject
    executor(_resolve, _reject)
  }

  // then方法,接收一个成功的回调和一个失败的回调
  then(resolveFn, rejectFn) {
    // 根据规范,如果then的参数不是function,则我们需要忽略它, 让链式调用继续往下执行
    typeof resolveFn !== 'function' ? resolveFn = value => value : null
    typeof rejectFn !== 'function' ? rejectFn = reason => {
      throw new Error(reason instanceof Error? reason.message:reason);
    } : null
  
    // return一个新的promise
    return new MyPromise((resolve, reject) => {
      // 把resolveFn重新包装一下,再push进resolve执行队列,这是为了能够获取回调的返回值进行分类讨论
      const fulfilledFn = value => {
        try {
          // 执行第一个(当前的)Promise的成功回调,并获取返回值
          let x = resolveFn(value)
          // 分类讨论返回值,如果是Promise,那么等待Promise状态变更,否则直接resolve
          x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
        } catch (error) {
          reject(error)
        }
      }
  
      // reject同理
      const rejectedFn  = error => {
        try {
          let x = rejectFn(error)
          x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
        } catch (error) {
          reject(error)
        }
      }
  
      switch (this._status) {
        // 当状态为pending时,把then回调push进resolve/reject执行队列,等待执行
        case PENDING:
          this._resolveQueue.push(fulfilledFn)
          this._rejectQueue.push(rejectedFn)
          break;
        // 当状态已经变为resolve/reject时,直接执行then回调
        case FULFILLED:
          fulfilledFn(this._value)    // this._value是上一个then回调return的值(见完整版代码)
          break;
        case REJECTED:
          rejectedFn(this._value)
          break;
      }
    })
  }

  //catch方法其实就是执行一下then的第二个回调
  catch(rejectFn) {
    return this.then(undefined, rejectFn)
  }

  //finally方法
  finally(callback) {
    return this.then(
      value => MyPromise.resolve(callback()).then(() => value),             //执行回调,并returnvalue传递给后面的then
      reason => MyPromise.resolve(callback()).then(() => { throw reason })  //reject同理
    )
  }

  //静态的resolve方法
  static resolve(value) {
    if(value instanceof MyPromise) return value //根据规范, 如果参数是Promise实例, 直接return这个实例
    return new MyPromise(resolve => resolve(value))
  }

  //静态的reject方法
  static reject(reason) {
    return new MyPromise((resolve, reject) => reject(reason))
  }

  //静态的all方法
  static all(promiseArr) {
    let index = 0
    let result = []
    return new MyPromise((resolve, reject) => {
      promiseArr.forEach((p, i) => {
        //Promise.resolve(p)用于处理传入值不为Promise的情况
        MyPromise.resolve(p).then(
          val => {
            index++
            result[i] = val
            if(index === promiseArr.length) {
              resolve(result)
            }
          },
          err => {
            reject(err)
          }
        )
      })
    })
  }

  //静态的race方法
  static race(promiseArr) {
    return new MyPromise((resolve, reject) => {
      //同时执行Promise,如果有一个Promise的状态发生改变,就变更新MyPromise的状态
      for (let p of promiseArr) {
        MyPromise.resolve(p).then(  //Promise.resolve(p)用于处理传入值不为Promise的情况
          value => {
            resolve(value)        //注意这个resolve是上边new MyPromise的
          },
          err => {
            reject(err)
          }
        )
      }
    })
  }
}

实现 Promise.all()

实现 Promise.race()

Scheduler 请求数量调度器

题目描述: JS 实现一个带并发限制的异步调度器 Scheduler,保证同时运行的任务最多有两个

 addTask(1000,"1");
 addTask(500,"2");
 addTask(300,"3");
 addTask(400,"4");
 的输出顺序是:2 3 1 4

 整个的完整执行流程:

一开始12两个任务开始执行
500ms时,2任务执行完毕,输出2,任务3开始执行
800ms时,3任务执行完毕,输出3,任务4开始执行
1000ms时,1任务执行完毕,输出1,此时只剩下4任务在执行
1200ms时,4任务执行完毕,输出4

实现代码如下:

class Scheduler {
  constructor(limit) {
    this.maxCount = limit;
    this.runCounts = 0;
    this.queue = [];
  }
  
  add(time, order) {
    const p = () => {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          console.log(order);
          resolve();
        }, time);
      });
    };
    this.queue.push(p);
  }
  
  taskStart() {
  	const request = () => {
      if (!this.queue || !this.queue.length || this.runCounts >= this.maxCount) {
        return;
      }
      this.runCounts++;
    	const p = this.queue.shift()
      p().then(() => {
        this.runCounts--;
        // 执行完继续递归判断
        this.request();
      });
    }
    
    for (let i = 0; i < this.maxCount; i++) {
      request();
    }
  }
}
const scheduler = new Scheduler(2);
const addTask = (time, order) => {
  scheduler.add(time, order);
};
addTask(1000, "1");
addTask(500, "2");
addTask(300, "3");
addTask(400, "4");
scheduler.taskStart();

limit 请求数限制

/**
 * 关键点
 * 1. new promise 一经创建,立即执行
 * 2. 使用 Promise.resolve().then 可以把任务加到微任务队列,防止立即执行迭代方法
 * 3. 微任务处理过程中,产生的新的微任务,会在同一事件循环内,追加到微任务队列里
 * 4. 使用 race 在某个任务完成时,继续添加任务,保持任务按照最大并发数进行执行
 * 5. 任务完成后,需要从 doingTasks 中移出
 */
function limit(count, array, iterateFunc) {
  const tasks = []
  const doingTasks = []
  let i = 0
  const enqueue = () => {
    if (i === array.length) {
      return Promise.resolve()
    }
    const task = Promise.resolve().then(() => iterateFunc(array[i++]))
    tasks.push(task)
    const doing = task.then(() => doingTasks.splice(doingTasks.indexOf(doing), 1))
    doingTasks.push(doing)
    const res = doingTasks.length >= count ? Promise.race(doingTasks) : Promise.resolve()
    return res.then(enqueue)
  };
  return enqueue().then(() => Promise.all(tasks))
}

// test
const timeout = i => new Promise(resolve => setTimeout(() => resolve(i), i))
limit(2, [1000, 1000, 1000, 1000], timeout).then((res) => {
  console.log(res)
})

arrange进行时间和工作调度

function arrange (name) {
    const task = [];
    task.push(() => {
        console.log(`${name} is notified`);
    })

    async function execute () {
        for (callback of task) {
            await callback();
        }
    }

    function doSomething (method) {
        task.push(() => {
            console.log(`Start to ${method}`);
        })
        return this;
    }

    function wait (time) {
        task.push(() => {
            new Promise((resolve, reject) => {
                console.log(`等待${time}秒`);
                setTimeout(resolve, time);
            })
        })
        return this;
    }

    function waitFirst (time) {
        task.unshift(() => {
            new Promise((resolve, reject) => {
                console.log(`等待${time}秒`)
                setTimeout(resolve, time);
            })
        })
        return this;
    }

    return {
        execute,
        do: doSomething,
        wait,
        waitFirst
    }
}

// arrange('William').execute();
// arrange('William').do('commit').execute();
// arrange('William').wait(5).do('commit').execute();
arrange("William").waitFirst(5).do("push").execute();

取消promise 手动

function myCancelPromiseWrap (p) {
  let cancel;
  let p1 = new Promise((resolve, reject) => {
    cancel = () => reject('取消');
  })

	let p2 = Promise.race([p1, p])
  p2.cancel = cancel

  return p2
}

let p = myCancelPromiseWrap(new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(123)
  }, 5000)
})) 

p.cancel();

取消promise promise.race

function fn1() {//模仿一个异步函数,此函数一秒以后执行完,返回成功的promise
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(777)
    }, 1000)
  })
}

function timerCtrl(time) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve("超时") //如果在秒数要求到达还未执行完
    }, time)
  })
}

function timerCancelPromise(fn1, time) {//传入要监听的函数和秒数要求
  return new Promise((resolve, reject) => {
    Promise.race([timerCtrl(time), fn1()]).then((result) => {
      if (result == '超时') {
        reject("超时");
      } else {
        resolve(result);
      } 
    })
  })
}

timerCancelPromise(fn1, 2000).then((res) => {
  console.log("成功", res)
}, (err) => {
  console.log("失败", err)
})

取消promise setTimeout

function fn1() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve();
        }, 1000)
    })
}

function timerOut (fn, timer) {
    return new Promise((resolve, reject) => {
        fn.then(() => {
            resolve();
        })

        setTimeout(() => {
            reject();
        }, timer)
    })
}

timerOut(fn1, 2000).then(() => {
    console.log('成功');
}).catch(() => {
    console.log('超时');
})

timeSleep

async function timeSleep(delay) {
  return await new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve("执行完毕")
    }, delay);
  })
}

每隔3s打印3

function sleep(timeout) {
  return new Promise((resolve, reject) => {
    setTimeout(resolve, timeout)
  })
}

async function myConsole() {
  for (let i = 1; ; i++) {
    if (i % 3 === 0) {
      await sleep(3000)
      console.log(3);
    } else {
      await sleep(i % 3 * 1000)
      console.log(i % 3);
    }
  }
}

myConsole();

红绿黄灯循环

const lightTask = (light, timer) => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if (light === 'red')  red();
            if (light === 'yellow') yellow();
            if (light === 'green') green();
            resolve();
        }, timer)
    })
}

const step = () => {
    lightTask('red', 3000)
      .then(() => lightTask('green', 1000))
      .then(() => lightTask('yellow', 2000))
      .then(step);
}

step();

异步 asyncAdd 写 sum

// 提供一个异步 add 方法如下,需要实现一个 async sum(...nums) 函数:
function asyncAdd(a, b, callback) {
    setTimeout(function() {
        callback(null, a + b)
    }, 1000)
}

// 异步用 promise 包一下  再用 async await 就能同步执行了
function promiseAdd (num1, num2) {
	return new Promise((resolve, reject) => {
        asyncAdd(num1, num2, (err, rs) => {
            if (err) {
                reject(err);
            } else {
                resolve(rs);
            }
        })
  })
}

async function sum(...nums) {
    let res = 0;
    for (const n of nums) {
        res = await promiseAdd(res, n);
    }
    
    return res;
}

// ================ 测试 ================
sum(1,2,3,4,5,6,7,8,9,10,11).then(total => console.log(total)) // 66
// or
async function test() {
    console.log(await sum(1,2,3,4,5,6,7,8,9,10,11))
}
test() // 66

// ================ 串行 执行 ================
async function serialSum(...nums) {
    return nums.reduce((task, cur) => {
        return task.then(res => promiseAdd(res, cur));
    }, Promise.resolve(0))
}

// ================ 并行 执行 ================
async function parallelSum(...nums) {
    if (nums.length === 1) return nums[0];
    const tasks = [];
    for (let i = 0; i < nums.length; i += 2) {
        tasks.push(promiseAdd(nums[i], nums[i + 1] || 0))
    }
    const results = await Promise.all(tasks)
    return parallelSum(...results);
}

async/await

实际上是对 Generator(生成器)的封装,是一个语法糖

差异:

  • async/await自带执行器,不需要手动调用next()就能自动执行下一步
  • async函数返回值是Promise对象,而Generator返回的是生成器对象
  • await能够返回Promise的resolve/reject的值

优点: 相比直接使用 Promise 来说,优势在于处理 then 的调用链,能够更清晰准确的写出代码

缺点:因为 await 将异步代码改造成了同步代码,如果多个异步代码没有依赖性却使用了 await 会导致性能上的降低

regeneratorRuntime.mark 和 regeneratorRuntime.wrap,这两者其实是 regenerator-runtime 模块里的两个方法,regenerator-runtime 模块来自 facebook 的 regenerator 模块, runtime.js

Generator 实现的核心在于上下文的保存,函数并没有真的被挂起,每一次yield,其实都执行了一遍传入的生成器函数,只是在这个过程中间用了一个context对象储存上下文,使得每次执行生成器函数的时候,都可以从上一个执行结果开始执行,看起来就像函数被挂起了一样