Promise方法存档

66 阅读7分钟

以下是我自己的写法,并不保证是标准的写法,答案仅做参考

Promise基础
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";

class MyPromise {
  constructor(executor) {
    this.status = PENDING;
    this.value = undefined;
    this.reason = undefined;
    this.onFulfilledCallbacks = [];
    this.onRejectedCallbacks = [];

    const resolve = (value) => {
      if (this.status === PENDING) {
        this.status = FULFILLED;
        this.value = value;

        this.onFulfilledCallbacks.map(cb => cb());
      }
    };
    const reject = (reason) => {
      if (this.status === PENDING) {
        this.status = REJECTED;
        this.reason = reason;

        this.onRejectedCallbacks.map(cb => cb());
      }
    };

    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;};

    const resolvePromise = (result, resolve, reject) => {
      if (result instanceof MyPromise) {
        result.then(resolve, reject);//透传状态
      } else {
        resolve(result); //转换成fulfilled
      }
    };
    //这里可以看出来 then方法本质上是返回一个Promise对象实例
    const newPromise = new MyPromise((resolve, reject) => {
      const fulfilledFunc = () =>
        setTimeout(() => {
        //这里每个fulfilledFunc/rejectedFunc都是异步微任务,保证所有的顺序都保持一致
        //而不是因为有微任务有同步任务而打乱最后的执行顺序
          try {
            const result = onFulfilled(this.value);
            resolvePromise(result, resolve, reject);
          } catch (e) {
            reject(e);
          }
        }, 0);
      const rejectedFunc = () => {
        setTimeout(() => {
          try {
            const result = onRejected(this.reason);
            resolvePromise(result, resolve, reject);
          } catch (e) {
            reject(e);
          }
        }, 0);
      };

      if (this.status === PENDING) {
        this.onFulfilledCallbacks.push(fulfilledFunc);
        this.onRejectedCallbacks.push(rejectedFunc);
      } else if (this.status === FULFILLED) {
        fulfilledFunc();
      } else {
        rejectedFunc();
      }
    });
    return newPromise; //链式调用的本质
  }
  catch(onRejected) {
    return this.then(null,onRejected)
  }
}

Promise 链式调用的核心本质是:then/catch 方法每次调用都会返回一个全新的 Promise 实例,且新 Promise 的状态由上一个 then/catch 回调的返回值决定;这里每个fulfilledFunc/rejectedFunc都是异步微任务,保证所有的顺序都保持一致,而不是因为有微任务有同步任务而打乱最后的执行顺序

Promise.all

实现Promise.all方法:

  1. 接收一个可迭代对象(如数组),返回一个新 Promise;
  2. 所有 Promise 都成功时,返回结果数组(顺序和输入一致);
  3. 任意一个 Promise 失败时,立即返回该失败原因;
  4. 若输入为空数组,立即 resolve 空数组。
const myPromiseAll = (arrFunc) => {
  return new Promise((resolve, reject) => {
    let resArr = [];
    let len = arrFunc.length;
    let k = 0;
    arrFunc.map((item, index) => {
      Promise.resolve(item).then(
        (res) => {
          k++;
          resArr[index] = res;
          if (k === len) {
            resolve(resArr);
          }
        },
        (rej) => {
          reject(rej);
        }
      );
    });
  });
};
const p1 = Promise.resolve(1);
const p2 = new Promise((resolve) => setTimeout(() => resolve(2), 1000));
const p3 = 3; // 非Promise值

myPromiseAll([p1, p2, p3])
  .then((res) => {
    console.log("all res:", JSON.stringify(res)); // [1,2,3]
  })
  .catch((err) => {
    console.log("all err:", err);
  });
Promise.race

实现Promise.race方法:

  1. 接收可迭代对象,返回新 Promise;
  2. 第一个完成(成功 / 失败)的 Promise 的结果 / 原因,就是最终结果;
  3. 若输入为空数组,永远 pending
const MyPromiseRace = (arrFunc) => {
  return new Promise((resolve,reject) => {
    arrFunc.map(item => {
      Promise.resolve(item).then(res=>{
        resolve(res)
      },rej=>{
        reject(rej)
      })
    });   
  });
};

const p1 = Promise.resolve(1);
const p2 = new Promise((resolve) => setTimeout(() => resolve(2), 1000));
const p3 = 3; // 非Promise值

const p4 = Promise.reject(new Error('fail'));
MyPromiseRace([p1, p4]).catch(err => {
  console.log('all err:', err.message); // fail
});
Promise.allSettled

实现Promise.allSettled方法:

  1. 接收可迭代对象,返回新 Promise;
  2. 所有 Promise 都完成(成功 / 失败)后,返回结果数组;
  3. 每个结果对象格式:{ status: 'fulfilled', value: xxx }{ status: 'rejected', reason: xxx }
  4. 空数组立即 resolve 空数组
const MyPromiseAllSettled = (arrFunc)=>{
  return new Promise((resolve,reject)=>{
    let len = arrFunc.length
    let resarr = []
    let k = 0

    arrFunc.map((item,index)=>{
      Promise.resolve(item).then(res=>{
        k++
        resarr[index] = res
        if(len === k){
          resolve(resarr)
        }
      },rej=>{
        k++
        resarr[index] = rej
        if(len === k){
          resolve(resarr)
        }
      })
    })
  })
}
const p1 = Promise.resolve(1);
const p2 = new Promise((resolve) => setTimeout(() => resolve(2), 1000));
const p3 = 3; // 非Promise值

const p4 = Promise.reject(new Error('fail'));
MyPromiseAllSettled([p1,p2,p3, p4]).then(res => {
  console.log('all err:', res); // fail
});
实现异步函数串行执行(基于 Promise)

给定一个异步函数数组(每个函数返回 Promise),实现一个函数,让这些函数串行执行(前一个完成后,再执行后一个),最终返回所有结果的数组。

这题豆包给出的答案是用arr.reduce,但是前一个完成后,再执行后一个第一时间我想到是async和await,不过async和await只能用for of或者for in遍历,不能使用arr.map/ arr.forEach完成,不然实现不了await的串行效果

const AsyncPromiseArr = (arrFunc) => {
  return new Promise(async(resolve, reject) => {
    const stack = [];
    let i = 0
    for(let func of arrFunc){
      const res = await func();
      stack.push(res);
      i++
      if(i === arrFunc.length){
        resolve(stack)
      }
    }
  });
};

// 测试用例
// 模拟异步函数
const task1 = () => new Promise(resolve => setTimeout(() => resolve(1), 500));
const task2 = () => new Promise(resolve => setTimeout(() => resolve(3), 1500));
const task3 = () => new Promise(resolve => setTimeout(() => resolve(2), 100));

AsyncPromiseArr([task1, task2, task3]).then(res => {
  console.log('serial res:', res); // [1,2,3](执行顺序:task1→task2→task3)
});
实现 Promise 超时封装

实现一个函数withTimeout(promise, timeout, errMsg)

  1. 给 Promise 添加超时控制,若超过timeout毫秒未完成,返回 reject(错误信息为errMsg);
  2. 若 Promise 在超时前完成,正常返回结果;
  3. 超时后终止等待(无需取消原 Promise,仅控制返回结果)。
const withTimeout = (promise,timeout,errMsg)=>{
  const promise1 = new Promise((resolve,reject)=>{
    setTimeout(()=>reject(new Error(errMsg)),timeout)
  })
  return Promise.race([promise,promise1])
}

const p1 = new Promise(resolve => setTimeout(() => resolve(100), 500));
withTimeout(p1, 1000, '超时了').then(res => {
  console.log('timeout res:', res); // 100
});

// 超时场景
const p2 = new Promise(resolve => setTimeout(() => resolve(200), 1500));
withTimeout(p2, 1000, '超时了').catch(err => {
  console.log('timeout err:', err.message); // 超时了
});
实现一个防抖函数

触发事件后,第一次触发立即执行,后续延迟 n 毫秒执行回调函数;如果在这 n 毫秒内再次触发事件,重置延迟时间,同时增加cancel方法支持手动取消

const debounce_ = (fn,delay)=>{
  let fn_
  const debounceFunc = function(...args){
    if(fn_){
      clearTimeout(fn_)
    }else{
      fn.call(this,...args)
    }
    fn_ = setTimeout(()=>{
      fn.call(this,...args)
      fn_ = null
    },delay)
  }
  debounceFunc.cancel = function(){
    clearImmediate(fn_)
    fn_ = null
  }
  return debounceFunc
}

function handleSearch(val) {
  console.log('搜索:', val);
}

const debouncedSearchImmediate = debounce_(handleSearch, 1000);
debouncedSearchImmediate('a'); // 立即输出「搜索:a」
debouncedSearchImmediate('ab'); // 500ms内触发,重置定时器(无输出)
setTimeout(() => {
  debouncedSearchImmediate('abc'); // 500ms后触发,立即输出「搜索:abc」
},2000);

// 测试取消功能
const debouncedCancel = debounce_(handleSearch, 500);
debouncedCancel('test');
debouncedCancel.cancel(); 
实现节流函数

触发事件后,每隔 n 毫秒只能执行一次回调函数,无论期间触发多少次,都会按固定频率执行

const throttle_ = (fn, delay) => {
  let timer;
  return function (...args) {
    if (!timer) {
      timer = setTimeout(() => {
        fn.call(this, ...args);
        timer = null
      }, delay);
    }
  };
};
实现带 Promise 的防抖函数

实现一个防抖函数debounceWithPromise(fn, delay)

  1. 触发后延迟delay毫秒执行函数;
  2. 重复触发时重置延迟;
  3. 函数执行结果通过 Promise 返回;
  4. 支持取消防抖(添加cancel方法)。
const debounceWithPromise = (fn, delay) => {
  let timer;
  let rejectFn;
  const debounce_ = function (...args) {
    if (timer) {
      clearTimeout(timer);
      rejectFn && rejectFn(new Error("Debounce reject"));
    }
    return new Promise((resolve, reject) => {
      rejectFn = reject
      timer = setTimeout(() => {
        try {
          const res = fn.call(this, ...args);
          Promise.resolve(res).then(resolve, reject);
        } catch (e) {
          reject(e);
        } finally {
          rejectFn = null;
          timer = null;
        }
      }, delay);
    });
  };
  debounce_.cancel = function () {
    if (timer) {
      clearTimeout(timer);
      timer = null;
      rejectFn && rejectFn(new Error("Debounce reject"));
      rejectFn = null;
    }
  };
  return debounce_;
};

const fn = (num) =>
  new Promise((resolve) => setTimeout(() => resolve(num * 2), 100));
const debouncedFn = debounceWithPromise(fn, 500);

// 重复触发
debouncedFn(1).catch((err) => console.log("err1:", err.message)); // Debounce reset
debouncedFn(2).then((res) => console.log("debounce res1:", res)); 
setTimeout(() => {
  debouncedFn(3).then((res) => console.log("debounce res2:", res)); 
}, 600);
基于 Promise.race 实现 “超时重试” 函数

实现一个raceWithRetry函数,满足:

  1. 接收 4 个参数:asyncTask(异步任务,返回 Promise)、timeout(超时时间,ms)、maxRetry(最大重试次数)、retryDelay(重试间隔,ms);
  2. 执行asyncTask,若超时 / 失败则自动重试,直到成功或重试次数耗尽;
  3. 每次重试前等待retryDelay毫秒;
  4. 最终返回成功的结果,或重试耗尽后抛出错误。
const raceWithRetry = (asyncTask, timeout, maxRetry, retryDelay) => {
  let cnt = maxRetry;
  return new Promise((resolve, reject) => {
    let promise1 = () =>
      new Promise((_, reject_) =>
        setTimeout(() => reject_(new Error("超时啦")), timeout)
      );
    const withRetry = () => {
      Promise.race([asyncTask(), promise1()]).then(
        (res) => {
          resolve(res);
        },
        (rej) => {
          if (cnt) {
            cnt--;
            setTimeout(() => withRetry(), retryDelay);
          } else {
            reject(rej);
          }
        }
      );
    };
    withRetry();
  });
};

// 模拟异步任务:随机成功/失败(测试重试)
function mockApi() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const random = Math.random();
      if (random > 0.7) {
        // 30%概率成功
        resolve("API request success");
      } else {
        reject(new Error("API request failed"));
      }
    }, 100); // 任务执行时间300ms
  });
}

// 测试1:超时重试(任务执行时间300ms,超时200ms,最大重试2次)
raceWithRetry(mockApi, 200, 2, 500)
  .then((res) => console.log("retry1:", res)) // 重试1-2次后成功,输出结果
  .catch((err) => console.log("retry1 err:", err.message)); // 若重试耗尽,输出错误
多接口竞速 + 超时 + 重试

实现一个fastestApiWithRetry函数,满足:

  1. 接收多个接口请求函数(数组)、单次超时时间、最大重试次数;
  2. 并行执行所有接口请求,用Promise.race最快的成功结果
  3. 若所有接口都超时 / 失败,则整体重试(最多maxRetry次);
  4. 最终返回最快的有效结果,或重试耗尽后抛出错误;
  5. 要求:即使某一个接口失败,不影响其他接口的竞速(仅忽略失败的接口)。