前端面试题之Promise场景

117 阅读5分钟

更多文章可看专栏:juejin.cn/column/7423…

一、手写简单版Promise

const PENDING = 'PENDING';
const FULFILLED = 'FULFILLED';
const REJECTED = 'REJECTED';

class Promise {
  constructor(executor) {
    this.status = PENDING;
    this.value = undefined;
    this.reason = undefined;
    // 存放成功的回调
    this.onResolvedCallbacks = [];
    // 存放失败的回调
    this.onRejectedCallbacks= [];

    let resolve = (value) => {
      if(this.status ===  PENDING) {
        this.status = FULFILLED;
        this.value = value;
        // 依次将对应的函数执行
        this.onResolvedCallbacks.forEach(fn=>fn());
      }
    } 

    let reject = (reason) => {
      if(this.status ===  PENDING) {
        this.status = REJECTED;
        this.reason = reason;
        // 依次将对应的函数执行
        this.onRejectedCallbacks.forEach(fn=>fn());
      }
    }

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

  then(onFulfilled, onRejected) {
    if (this.status === FULFILLED) {
      onFulfilled(this.value)
    }

    if (this.status === REJECTED) {
      onRejected(this.reason)
    }

    if (this.status === PENDING) {
      // 如果promise的状态是 pending,需要将 onFulfilled 和 onRejected 函数存放起来,等待状态确定后,再依次将对应的函数执行
      this.onResolvedCallbacks.push(() => {
        onFulfilled(this.value)
      });

      // 如果promise的状态是 pending,需要将 onFulfilled 和 onRejected 函数存放起来,等待状态确定后,再依次将对应的函数执行
      this.onRejectedCallbacks.push(()=> {
        onRejected(this.reason);
      })
    }
  }
}

二、Promise.all

1) 核心思路

  1. 接收一个 Promise 实例的数组或具有 Iterator 接口的对象作为参数
  2. 这个方法返回一个新的 promise 对象,
  3. 遍历传入的参数,用Promise.resolve()将参数"包一层",使其变成一个promise对象
  4. 参数所有回调成功才是成功,返回值数组与参数顺序一致
  5. 参数数组其中一个失败,则触发失败状态,第一个触发失败的 Promise 错误信息作为 Promise.all 的错误信息。

2)实现代码

一般来说,Promise.all 用来处理多个并发请求,也是为了页面数据构造的方便,将一个页面所用到的在不同接口的数据一起请求过来,不过,如果其中一个接口失败了,多个请求也就失败了,页面可能啥也出不来,这就看当前页面的耦合程度了

function PromiseAll(promiseArr) {
    return new Promise((resolve, reject) => {
        if (!Array.isArray(promiseArr)) {
            return reject(new Error('arr'))
        }
        const res = []
        let count = 0
        for (let i = 0; i < promiseArr.length; i++) {
            Promise.resolve(promiseArr[i]).then(value => {
                count++
                res[i] = value
                if (count === promiseArr.length) {
                    resolve(res)
                }
            }).catch(e => reject(e))
        }
    })
}
const p1 = new Promise((res, rej) => {
    setTimeout(() => {
        res('p1')
    }, 1000)
})
const p2 = new Promise((res, rej) => {
    setTimeout(() => {
        res('p2')
    }, 2000)
})
const p3 = new Promise((res, rej) => {
    setTimeout(() => {
        res('p3')
    }, 3000)
})
const test = PromiseAll([p2, p1, p3])
    .then(res => console.log(res))
    .catch(e => console.log(e))

console.log(test);

三、Promise.allSettled

function allSettled(promises) {
  return new Promise((resolve, reject) => {
    const results = [];
    const len = promises.length;
    let resolvedCount = 0;
    for (let i = 0; i < len; i++) {
      promises[i]
        .then((value) => {
          results[i] = { status: "fulfilled", value };
        })
        .catch((reason) => {
          results[i] = { status: "rejected", reason };
        })
        .finally(() => {
          resolvedCount++;
          if (resolvedCount === len) {
            resolve(results);
          }
        });
    }
  });
}

四、Promise.race

该方法的参数是 Promise 实例数组, 然后其 then 注册的回调方法是数组中的某一个 Promise 的状态变为 fulfilled 的时候就执行. 因为 Promise 的状态只能改变一次, 那么我们只需要把 Promise.race 中产生的 Promise 对象的 resolve 方法, 注入到数组中的每一个 Promise 实例中的回调函数中即可

Promise.race = function (args) {
  return new Promise((resolve, reject) => {
    for (let i = 0; i < args.length; i++) {
      args[i].then(resolve, reject)
    }
  })
}

五、Promise.any

使用场景

  1. 快速返回第一个成功的结果:当你有多个异步操作,并且只关心第一个成功的结果时,可以使用 Promise.any。例如,多个 API 请求,只需要第一个成功的响应。
  2. 提高容错能力:在某些情况下,你可能有多个备选方案,只要有一个成功就可以继续处理,而不需要等待所有的操作都完成。
function promiseAny(promises) {
  return new Promise((resolve, reject) => {
    if (promises.length === 0) {
      return reject(new AggregateError([], 'All promises were rejected'));
    }

    let rejectedCount = 0;
    const errors = [];

    promises.forEach((promise, index) => {
      Promise.resolve(promise)
        .then(resolve)
        .catch(error => {
          rejectedCount++;
          errors[index] = error;
          if (rejectedCount === promises.length) {
            reject(new AggregateError(errors, 'All promises were rejected'));
          }
        });
    });
  });
}

// Example usage
const promise1 = new Promise((resolve, reject) => setTimeout(reject, 100, 'Error 1'));
const promise2 = new Promise((resolve, reject) => setTimeout(resolve, 200, 'Result 2'));
const promise3 = new Promise((resolve, reject) => setTimeout(resolve, 300, 'Result 3'));

promiseAny([promise1, promise2, promise3])
  .then(result => {
    console.log(result); // "Result 2"
  })
  .catch(error => {
    console.error(error);
  });

六、Promise封装ajax请求

// promise 封装实现:
function myajax(url) {
  // 创建一个 promise 对象
  let promise = new Promise(function(resolve, reject) {
    let xhr = new XMLHttpRequest();
    // 新建一个 http 请求
    xhr.open("GET", url, true);
    // 设置状态的监听函数
    xhr.onreadystatechange = function() {
      if (this.readyState !== 4) return;
      // 当请求成功或失败时,改变 promise 的状态
      if (this.status === 200) {
        resolve(this.response);
      } else {
        reject(new Error(this.statusText));
      }
    };
    // 设置错误监听函数
    xhr.onerror = function() {
      reject(new Error(this.statusText));
    };
    // 设置响应的数据类型
    xhr.responseType = "json";
    // 设置请求头信息
    xhr.setRequestHeader("Accept", "application/json");
    // 发送 http 请求
    xhr.send(null);
  });
  return promise;
}

七、Promise实现sleep函数

function timeout(delay) {
  return new Promise(resolve => {
    setTimeout(resolve, delay)
  })
};

八、Promise错误间隔重试

function retry(promiseFn, times, interval) {
  return new Promise((resolve, reject) => {
    let count = 0;
    function run() {
      promiseFn()
        .then(resolve)
        .catch((error) => {
          count++;
          if (count === times) {
            reject(error);
          } else {
            setTimeout(run, interval);
          }
        });
    }
    run();
  });
}

九、Promise图片异步加载

let imageAsync=(url)=>{
    return new Promise((resolve,reject)=>{
        let img = new Image();
        img.src = url;
        img.οnlοad=()=>{
            console.log(`图片请求成功,此处进行通用操作`);
            resolve(image);
        }
        img.οnerrοr=(err)=>{
            console.log(`失败,此处进行失败的通用操作`);
            reject(err);
        }
    })
}
        
imageAsync("url").then(()=>{
    console.log("加载成功");
}).catch((error)=>{
    console.log("加载失败");
})

十、Promise实现红绿灯

const task = (timer, light) => {
  return new Promise((resolve, reject) => {
      setTimeout(() => {
          if (light === 'red') {
              red()
          }
          else if (light === 'green') {
              green()
          }
          else if (light === 'yellow') {
              yellow()
          }
          resolve()
      }, timer)
  })
}
const step = () => {
    task(3000, 'red')
        .then(() => task(2000, 'green'))
        .then(() => task(2100, 'yellow'))
        .then(step)
}
step()

十一、Promise串行请求

// 6s后输出[1,2,3]
const funcArr = [
  () =>
    new Promise((resolve) => {
      setTimeout(() => resolve(1), 2000);
    }),
  () =>
    new Promise((resolve) => {
      setTimeout(() => resolve(2), 1000);
    }),
  () =>
    new Promise((resolve) => {
      setTimeout(() => resolve(3), 3000);
    }),
];

// 函数最终返回一个promise
function runPromiseByQueue(promiseFuncArr) {
  const res = [];
  return new Promise((resolve, reject) => {
    promiseFuncArr
      .reduce((acc, cur) => acc.then(cur).then((data) => res.push(data)), Promise.resolve())
      // reduce函数最终返回一个promise,在onResolved这一步骤执行resolve,将结果输出
      .then(() => resolve(res));
  });
}

runPromiseByQueue(funcArr).then((res) => {
  console.log(res);
});

十二、实现Promisify函数

function promisify(fn) {
  return function(...args) {
    return new Promise((resolve, reject) => {
      fn(...args, (err, result) => {
        if (err) {
          reject(err);
        } else {
          resolve(result);
        }
      });
    });
  };
}
function exampleCallback(arg1, arg2, callback) {
  setTimeout(() => {
    if (arg1 > 0) {
      callback(null, `Success: ${arg1 + arg2}`);
    } else {
      callback('Error: arg1 must be greater than 0');
    }
  }, 1000);
}

// 使用 promisify 转换
const examplePromise = promisify(exampleCallback);

// 调用并处理 Promise
examplePromise(1, 2)
  .then(result => console.log(result)) // 输出: Success: 3
  .catch(error => console.error(error));

examplePromise(0, 2)
  .then(result => console.log(result))
  .catch(error => console.error(error)); // 输出: Error: arg1 must be greater than 0