Promise深入浅出

以案例引出Promise,深入浅出,合理

链式调用直接跳至第2章

静态方法跳转第3章

async、await跳转第4章

第5章为前四章的练习

1- Promise基础

张三的烦恼

张三心中有很多女神,他今天下定决心,要向这些女神表白,他认为,只要女神够多,根据概率学原理,总有一个会接收他

稳妥起见,张三决定使用串行的方式进行表白:先给第1位女神发送短信,然后等待女神的回应,如果成功了,就结束,如果失败了,则再给第2位女神发送短信,依次类推

张三的女神一共有4位,名字分别是:女神1、王富贵、周聚财、刘人勇

发短信是一个重复性的劳动,张三是个程序员,因此决定用函数封装这个动作

javascript 复制代码
// 向某位女生发送一则表白短信
// name: 女神的姓名
// onFulffiled: 成功后的回调
// onRejected: 失败后的回调
function sendMessage(name, onFulffiled, onRejected) {
  // 模拟 发送表白短信
  console.log(
    `张三 -> ${name}:最近有谣言说我喜欢你,我要澄清一下,那不是谣言😘`
  );
  console.log(`等待${name}回复......`);
  // 模拟 女神回复需要一段时间
  setTimeout(() => {
    // 模拟 有10%的几率成功
    if (Math.random() <= 0.1) {
      // 成功,调用 onFuffiled,并传递女神的回复
      onFulffiled(`${name} -> 张三:我是九,你是三,除了你还是你😘`);
    } else {
      // 失败,调用 onRejected,并传递女神的回复
      onRejected(`${name} -> 张三:你是个好人😜`);
    }
  }, 1000);
}

有了这个函数后,张三于是开始编写程序发送短信了

javascript 复制代码
// 首先向 女神1 发送消息
sendMessage(
  '女神1',
  (reply) => {
    // 如果成功了,输出回复的消息后,结束
    console.log(reply);
  },
  (reply) => {
    // 如果失败了,输出回复的消息后,向 王富贵 发送消息
    console.log(reply);
    sendMessage(
      '王富贵',
      (reply) => {
        // 如果成功了,输出回复的消息后,结束
        console.log(reply);
      },
      (reply) => {
        // 如果失败了,输出回复的消息后,向 周聚财 发送消息
        console.log(reply);
        sendMessage(
          '周聚财',
          (reply) => {
            // 如果成功了,输出回复的消息后,结束
            console.log(reply);
          },
          (reply) => {
            // 如果失败了,输出回复的消息后,向 刘人勇 发送消息
            console.log(reply);
            sendMessage(
              '刘人勇',
              (reply) => {
                // 如果成功了,输出回复的消息后,结束
                console.log(reply);
              },
              (reply) => {
                // 如果失败了,就彻底没戏了
                console.log(reply);
                console.log('张三命犯天煞孤星,注定孤独终老!!');
              }
            );
          }
        );
      }
    );
  }
);

该程序完成后,张三内心是崩溃的

这一层一层的回调嵌套,形成了传说中的「回调地狱 callback hell

张三是个完美主义者,怎么能忍受这样的代码呢?

要解决这样的问题,需要Promise出马

Promise规范

Promise是一套专门处理异步场景的规范,它能有效的避免回调地狱的产生,使异步代码更加清晰、简洁、统一

这套规范最早诞生于前端社区,规范名称为Promise A+

该规范出现后,立即得到了很多开发者的响应

Promise A+ 规定:

  1. 所有的异步场景,都可以看作是一个异步任务,每个异步任务,在JS中应该表现为一个对象 ,该对象称之为Promise对象,也叫做任务对象

  2. 每个任务对象,都应该有两个阶段、三个状态

    根据常理,它们之间存在以下逻辑:

    • 任务总是从未决阶段变到已决阶段,无法逆行
    • 任务总是从挂起状态变到完成或失败状态,无法逆行
    • 时间不能倒流,历史不可改写,任务一旦完成或失败,状态就固定下来,永远无法改变
  3. 挂起->完成,称之为resolve挂起->失败称之为reject。任务完成时,可能有一个相关数据;任务失败时,可能有一个失败原因。

  4. 可以针对任务进行后续处理,针对完成状态的后续处理称之为onFulfilled ,针对失败的后续处理称之为onRejected

Promise API

ES6提供了一套API,实现了Promise A+规范

基本使用如下:

javascript 复制代码
// 创建一个任务对象,该任务立即进入 pending 状态
const pro = new Promise((resolve, reject) => {
  // 任务的具体执行流程,该函数会立即被执行
  // 调用 resolve(data),可将任务变为 fulfilled 状态, data 为需要传递的相关数据
  // 调用 reject(reason),可将任务变为 rejected 状态,reason 为需要传递的失败原因
});
​
pro.then(
  (data) => {
    // onFulfilled 函数,当任务完成后,会自动运行该函数,data为任务完成的相关数据
  },
  (reason) => {
    // onRejected 函数,当任务失败后,会自动运行该函数,reason为任务失败的相关原因
  }
);

张三的解决方案

学习了ES6的Promise后,张三决定对sendMessage函数进行改造,改造结果如下:

javascript 复制代码
// 向某位女生发送一则表白短信
// name: 女神的姓名
// 该函数返回一个任务对象
function sendMessage(name) {
  return new Promise((resolve, reject) => {
    // 模拟 发送表白短信
    console.log(
      `张三 -> ${name}:最近有谣言说我喜欢你,我要澄清一下,那不是谣言😘`
    );
    console.log(`等待${name}回复......`);
    // 模拟 女神回复需要一段时间
    setTimeout(() => {
      // 模拟 有10%的几率成功
      if (Math.random() <= 0.1) {
        // 成功,调用 resolve,并传递女神的回复
        resolve(`${name} -> 张三:我是九,你是三,除了你还是你😘`);
      } else {
        // 失败,调用 reject,并传递女神的回复
        reject(`${name} -> 张三:你是个好人😜`);
      }
    }, 1000);
  });
}

之后,就可以使用该函数来发送消息了

javascript 复制代码
sendMessage('女神1').then(
  (reply) => {
    // 女神答应了,输出女神的回复
    console.log(reply);
  },
  (reason) => {
    // 女神拒绝了,输出女神的回复
    console.log(reason);
  }
);

至此,回调地狱的问题仍然没能解决

要解决回调地狱,还需要进一步学习Promise的知识

2- Promise的链式调用

catch方法

.catch(onRejected) = .then(null, onRejected)

链式调用

  1. then方法必定会返回一个新的Promise

    可理解为后续处理也是一个任务

  2. 新任务的状态取决于后续处理:

    • 若没有相关的后续处理,新任务的状态和前任务一致,数据为前任务的数据

    • 若有后续处理但还未执行,新任务挂起。

    • 若后续处理执行了,则根据后续处理的情况确定新任务的状态

      • 后续处理执行无错,新任务的状态为完成,数据为后续处理的返回值
      • 后续处理执行有错,新任务的状态为失败,数据为异常对象
      • 后续执行后返回的是一个任务对象,新任务的状态和数据与该任务对象一致

由于链式任务的存在,异步代码拥有了更强的表达力

scss 复制代码
// 常见任务处理代码
​
/*
 * 任务成功后,执行处理1,失败则执行处理2
 */
pro.then(处理1).catch(处理2)
​
/*
 * 任务成功后,依次执行处理1、处理2
 */
pro.then(处理1).then(处理2)
​
/*
 * 任务成功后,依次执行处理1、处理2,若任务失败或前面的处理有错,执行处理3
 */
pro.then(处理1).then(处理2).catch(处理3)

张三的解决方案

javascript 复制代码
// 向某位女生发送一则表白短信
// name: 女神的姓名
// onFulffiled: 成功后的回调
// onRejected: 失败后的回调
function sendMessage(name) {
  return new Promise((resolve, reject) => {
    // 模拟 发送表白短信
    console.log(
      `张三 -> ${name}:最近有谣言说我喜欢你,我要澄清一下,那不是谣言😘`
    );
    console.log(`等待${name}回复......`);
    // 模拟 女神回复需要一段时间
    setTimeout(() => {
      // 模拟 有10%的几率成功
      if (Math.random() <= 0.1) {
        // 成功,调用 onFuffiled,并传递女神的回复
        resolve(`${name} -> 张三:我是九,你是三,除了你还是你😘`);
      } else {
        // 失败,调用 onRejected,并传递女神的回复
        reject(`${name} -> 张三:你是个好人😜`);
      }
    }, 1000);
  });
}
​
sendMessage('女神1')
  .catch((reply) => {
    // 失败,继续
    console.log(reply);
    return sendMessage('王富贵');
  })
  .catch((reply) => {
    // 失败,继续
    console.log(reply);
    return sendMessage('周聚财');
  })
  .catch((reply) => {
    // 失败,继续
    console.log(reply);
    return sendMessage('刘人勇');
  })
  .then(
    (reply) => {
      // 成功,结束
      console.log(reply);
      console.log('张三终于找到了自己的伴侣');
    },
    (reply) => {
      // 最后一个也失败了
      console.log(reply);
      console.log('张三命犯天煞孤星,无伴终老,孤独一生');
    }
  );

3- Promise的静态方法

张三的新问题

张嫂出门时,给张三交待了几个任务:

  1. 做饭

    可交给电饭煲完成

  2. 洗衣服

    可交给洗衣机完成

  3. 打扫卫生

    可交给扫地机器人完成

张三需要在所有任务结束后给张嫂汇报工作,哪些成功了,哪些失败了

为了最大程度的节约时间,张三希望这些任务同时进行,最终汇总结果统一处理

每个任务可以看做是一个返回Promise的函数

javascript 复制代码
// 做饭
function cook() {
  return new Promise((resolve, reject) => {
    console.log('张三打开了电饭煲');
    setTimeout(() => {
      if (Math.random() < 0.5) {
        resolve('饭已ok');
      } else {
        reject('做饭却忘了加水,米饭变成了爆米花');
      }
    }, 2000);
  });
}
​
// 洗衣服
function wash() {
  return new Promise((resolve, reject) => {
    console.log('张三打开了洗衣机');
    setTimeout(() => {
      if (Math.random() < 0.5) {
        resolve('衣服已经洗好');
      } else {
        reject('洗衣服时停水了,洗了个寂寞');
      }
    }, 2500);
  });
}
​
// 打扫卫生
function sweep() {
  return new Promise((resolve, reject) => {
    console.log('张三打开了扫地机器人');
    setTimeout(() => {
      if (Math.random() < 0.5) {
        resolve('地板扫的非常干净');
      } else {
        reject('扫地机器人被哈士奇一爪掀翻了');
      }
    }, 3000);
  });
}
​

如何利用这三个函数实现张三的要求呢?

Promise的静态方法

方法名 含义
Promise.resolve(data) 直接返回一个完成状态的任务
Promise.reject(reason) 直接返回一个拒绝状态的任务
Promise.all(任务数组) 返回一个任务 任务数组全部成功则成功 任何一个失败则失败
Promise.any(任务数组) 返回一个任务 任务数组任一成功则成功 任务全部失败则失败
Promise.allSettled(任务数组) 返回一个任务 任务数组全部已决则成功 该任务不会失败
Promise.race(任务数组) 返回一个任务 任务数组任一已决则已决,状态和其一致

张三的解决方案

scss 复制代码
Promise.allSettled([cook(), wash(), sweep()]).then((result) => {
  // 处理汇总结果
  const report = result
    .map((r) => (r.status === 'fulfilled' ? r.value : r.reason))
    .join(';');
  console.log(report);
});

4- async和await

消除回调

有了Promise,异步任务就有了一种统一的处理方式

有了统一的处理方式,ES官方就可以对其进一步优化

ES7推出了两个关键字asyncawait,用于更加优雅的表达Promise

async

async关键字用于修饰函数,被它修饰的函数,一定返回Promise

javascript 复制代码
async function method1(){
  return 1; // 该函数的返回值是Promise完成后的数据
}
​
method1(); // Promise { 1 }
​
async function method2(){
  return Promise.resolve(1); // 若返回的是Promise,则method得到的Promise状态和其一致
}
​
method2(); // Promise { 1 }
​
async function method3(){
  throw new Error(1); // 若执行过程报错,则任务是rejected
}
​
method3(); // Promise { <rejected> Error(1) }

await

await关键字表示等待某个Promise完成,它必须用于async函数中

javascript 复制代码
async function method(){
  const n = await Promise.resolve(1);
  console.log(n); // 1
}
​
// 上面的函数等同于
function method(){
  return new Promise((resolve, reject)=>{
    Promise.resolve(1).then(n=>{
      console.log(n);
      resolve(1)
    })
  })
}

await也可以等待其他数据

csharp 复制代码
async function method(){
  const n = await 1; // 等同于 await Promise.resolve(1)
}

如果需要针对失败的任务进行处理,可以使用try-catch语法

javascript 复制代码
async function method(){
  try{
    const n = await Promise.reject(123); // 这句代码将抛出异常
    console.log('成功', n)
  }
  catch(err){
    console.log('失败', err)
  }
}
​
method(); // 输出: 失败 123

张三表白的完美解决方案

张三的女神可不是只有4位,而是40位!

为了更加方便的编写表白代码,张三决定把这40位女神放到一个数组中,然后利用async和await轻松完成代码

javascript 复制代码
// 女神的名字数组
const beautyGirls = [
  '梁平',
  '邱杰',
  '王超',
  ......
];
​
// 向某位女生发送一则表白短信
// name: 女神的姓名
function sendMessage(name) {
  return new Promise((resolve, reject) => {
    // 模拟 发送表白短信
    console.log(
      `张三 -> ${name}:最近有谣言说我喜欢你,我要澄清一下,那不是谣言😘`
    );
    console.log(`等待${name}回复......`);
    // 模拟 女神回复需要一段时间
    setTimeout(() => {
      // 模拟 有10%的几率成功
      if (Math.random() <= 0.1) {
        // 成功,调用 onFuffiled,并传递女神的回复
        resolve(`${name} -> 张三:我是九,你是三,除了你还是你😘`);
      } else {
        // 失败,调用 onRejected,并传递女神的回复
        reject(`${name} -> 张三:你是个好人😜`);
      }
    }, 1000);
  });
}
​
// 批量表白的程序
async function proposal() {
  let isSuccess = false;
  for (const girl of beautyGirls) {
    try {
      const reply = await sendMessage(girl);
      console.log(reply);
      console.log('表白成功!');
      isSuccess = true;
      break;
    } catch (reply) {
      console.log(reply);
      console.log('表白失败');
    }
  }
  if (!isSuccess) {
    console.log('张三注定孤独一生');
  }
}
proposal();

练习题

1-Promise基础练习

1-完成delay函数的补充

javascript 复制代码
//1. 完成下面的函数
​
/**
 * 延迟一段指定的时间
 * @param {Number} duration 等待的时间
 * @returns {Promise} 返回一个任务,该任务在指定的时间后完成
 */
function delay(duration) {
  //补充内容  
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve();
    }, duration);
  });
}
​
// 2. 按照要求,调用delay函数,完成程序
​
// 利用delay函数,等待1秒钟,输出:finish
delay(1000).then(() => {
  console.log('finish');
});

2- 完成图片操作

javascript 复制代码
// 根据指定的图片路径,创建一个img元素
// 该函数需要返回一个Promise,当图片加载完成后,任务完成,若图片加载失败,任务失败
// 任务完成后,需要提供的数据是图片DOM元素;任务失败时,需要提供失败的原因
// 提示:img元素有两个事件,load事件会在图像加载完成时触发,error事件会在图像加载失败时触发
function createImage(imgUrl) {}
​
// 使用createImage函数创建一个图像,图像路径自行定义
// 当图像成功加载后,将图像宽高显示在p元素中,当图像加载失败后,输出加载失败的原因
​
// 使用createImage函数创建一个图像,图像路径自行定义
// 当图像成功加载后,将图像元素加入到container容器中,当图像加载失败后,输出加载失败的原因

javascript 复制代码
function createImage(imgUrl) {
    return new Promise((resolve, reject) => {
        const img = document.createElement('img');
        img.src = imgUrl;
        img.onload = () => {
            // 图像加载完成
            resolve(img);
        };
        img.onerror = (e) => {
            // 图像加载失败
            reject(e);
        };
    });
}
​
// 使用createImage函数创建一个图像,图像路径自行定义
// 当图像成功加载后,将图像宽高显示在p元素中,当图像加载失败后,输出加载失败的原因
const url1 =
      'https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/151a34f082bf4d78b1c51a9642db29d1~tplv-k3u1fbpfcp-jj:240:200:0:0:q75.avis#?w=2080&h=960&s=929926&e=jpg&b=d4f3ec';
createImage(url1).then(
    (img) => {
        const p = document.querySelector('.label');
        p.innerHTML = `${img.width} * ${img.height}`;
    },
    (reason) => {
        console.log(reason);
    }
);
​
// 使用createImage函数创建一个图像,图像路径自行定义
// 当图像成功加载后,将图像元素加入到container容器中,当图像加载失败后,输出加载失败的原因
createImage(url1).then(
    (img) => {
        const div = document.querySelector('.container');
        div.appendChild(img);
    },
    (reason) => {
        console.log(reason);
    }
);

3- 完成省份信息渲染

javascript 复制代码
// 调用该函数,会远程加载省份数据
// 函数返回一个Promise,成功后得到省份数组,失败时会给予失败原因
function getProvinces() {
    return fetch('/api/citylist')
        .then((resp) => resp.json())
        .then((resp) => resp.data)
        .then((resp) =>
            resp.map((it) => ({ value: it.value, label: it.label }))
         );
}
​
// 利用getProvinces函数,将省份数据加载到select元素中
getProvinces().then(
    (ps) => {
        const html = ps
        .map((p) => `<option value="${p.value}">${p.label}</option>`)
        .join('');
        const selProvince = document.getElementById('selProvince');
        selProvince.innerHTML = html;
    },
    (reason) => {
        console.log(reason);
    }
);

3-Promise链式调用练习

1-获取学生数据

javascript 复制代码
/**
 * 根据页码获取学生数据,返回Promise
 * @param {Number} page 页码
 */
function fetchStudents(page) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (Math.random() < 0.3) {
        reject(new Error(`网络错误!获取第${page}页数据失败!`));
        return;
      }
      // 模拟学生数据
      const stus = new Array(10).fill(null).map((d, i) => ({
        id: `NO.${(page - 1) * 10 + i + 1}`,
        name: `姓名${(page - 1) * 10 + i + 1}`,
      }));
      resolve(stus);
    }, Math.floor(Math.random() * 5000));
  });
}
​
// 利用 fetchStudents 函数,完成下面的练习
​
// 获取1-10页的学生,最终按照页码的顺序合并成一个数组,任何一页的数据获取出现错误,则任务不再继续,打印错误消息
​
// 获取1-10页的学生,最终按照页码的顺序合并成一个数组,如果某些页码的数据获取失败,就不加入该数据即可
​
// 获取1-10页的学生,打印最先获取到的数据,如果全部都获取失败,则打印所有的错误消息
​
// 获取1-10页的学生,输出最先得到的结果(有结果输出结果,有错误输出错误)

javascript 复制代码
// 获取1-10页的学生,最终按照页码的顺序合并成一个数组,任何一页的数据获取出现错误,则任务不再继续,打印错误消息
const proms = new Array(10).fill(1).map((it, i) => fetchStudents(i + 1));
​
Promise.all(proms)
  .then((result) => {
    console.log(result.flat());
  })
  .catch((err) => {
    console.log(err);
  });
​
// 获取1-10页的学生,最终按照页码的顺序合并成一个数组,如果某些页码的数据获取失败,就不加入该数据即可
Promise.allSettled(proms).then((result) => {
  result = result
    .filter((r) => r.status === 'fulfilled')
    .map((it) => it.value)
    .flat();
  console.log(result);
});
​
// 获取1-10页的学生,打印最先获取到的数据,如果全部都获取失败,则打印所有的错误消息
Promise.any(proms)
  .then((result) => {
    console.log(result);
  })
  .catch((err) => {
    console.log(err.errors);
  });
​
// 获取1-10页的学生,输出最先得到的结果(有结果输出结果,有错误输出错误)
Promise.race(proms).then(
  (result) => {
    console.log(result);
  },
  (err) => {
    console.log(err);
  }
);

4-aysnc和await练习

1-获取英雄数据渲染

javascript 复制代码
async function getHeroes() {
    return fetch('/api/herolist')
        .then((resp) => resp.json())
        .then((resp) => resp.data);
}
// 利用getHeroes方法,获取所有的英雄数据,将英雄名称显示到页面的列表中

ini 复制代码
// 利用getHeroes方法,获取所有的英雄数据,将英雄名称显示到页面的列表中
const ul = document.getElementById('heroList');
(async () => {
    const data = await getHeroes();
    const result = data.map((d) => `<li>${d.cname}</li>`).join('');
    ul.innerHTML = result;
})();

2-完成delay函数

javascript 复制代码
// 完成delay函数
// 该函数可以等待一段指定的时间
// 返回Promise
function delay(duration) {}
​
// 利用delay函数,等待3次,每次等待1秒,每次等待完成后输出ok
// 等待1秒->ok->等待1秒->ok->等待1秒->ok

scss 复制代码
// 利用delay函数,等待3次,每次等待1秒,每次等待完成后输出ok
// 等待1秒->ok->等待1秒->ok->等待1秒->ok
(async () => {
  for (let i = 0; i < 3; i++) {
    await delay(1000);
    console.log('ok');
  }
})();
​
delay(1000)
  .then(() => {
    console.log('ok');
    return delay(1000);
  })
  .then(() => {
    console.log('ok');
    return delay(1000);
  })
  .then(() => {
    console.log('ok');
  });

手写Promise

kotlin 复制代码
// 记录Promise的三种状态
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
​
/**
 * 运行一个微队列任务
 * 把传递的函数放到微队列中
 * @param {Function} callback
 */
function runMicroTask(callback) {
  // 判断node环境
  // 为了避免「变量未定义」的错误,这里最好加上前缀globalThis
  // globalThis是一个关键字,指代全局对象,浏览器环境为window,node环境为global
  if (globalThis.process && globalThis.process.nextTick) {
    process.nextTick(callback);
  } else if (globalThis.MutationObserver) {
    const p = document.createElement('p');
    const observer = new MutationObserver(callback);
    observer.observe(p, {
      childList: true, // 观察该元素内部的变化
    });
    p.innerHTML = '1';
  } else {
    setTimeout(callback, 0);
  }
}
​
/**
 * 判断一个数据是否是Promise对象
 * @param {any} obj
 * @returns
 */
function isPromise(obj) {
  return !!(obj && typeof obj === 'object' && typeof obj.then === 'function');
}
​
class MyPromise {
  /**
   * 创建一个Promise
   * @param {Function} executor 任务执行器,立即执行
   */
  constructor(executor) {
    this._state = PENDING; // 状态
    this._value = undefined; // 数据
    this._handlers = []; // 处理函数形成的队列
    try {
      executor(this._resolve.bind(this), this._reject.bind(this));
    } catch (error) {
      this._reject(error);
      console.error(error);
    }
  }
​
  /**
   * 向处理队列中添加一个函数
   * @param {Function} executor 添加的函数
   * @param {String} state 该函数什么状态下执行
   * @param {Function} resolve 让then函数返回的Promise成功
   * @param {Function} reject 让then函数返回的Promise失败
   */
  _pushHandler(executor, state, resolve, reject) {
    this._handlers.push({
      executor,
      state,
      resolve,
      reject,
    });
  }
​
  /**
   * 根据实际情况,执行队列
   */
  _runHandlers() {
    if (this._state === PENDING) {
      // 目前任务仍在挂起
      return;
    }
    while (this._handlers[0]) {
      const handler = this._handlers[0];
      this._runOneHandler(handler);
      this._handlers.shift();
    }
  }
​
  /**
   * 处理一个handler
   * @param {Object} handler
   */
  _runOneHandler({ executor, state, resolve, reject }) {
    runMicroTask(() => {
      if (this._state !== state) {
        // 状态不一致,不处理
        return;
      }
​
      if (typeof executor !== 'function') {
        // 传递后续处理并非一个函数
        this._state === FULFILLED ? resolve(this._value) : reject(this._value);
        return;
      }
      try {
        const result = executor(this._value);
        if (isPromise(result)) {
          result.then(resolve, reject);
        } else {
          resolve(result);
        }
      } catch (error) {
        reject(error);
        console.error(error);
      }
    });
  }
​
  /**
   * Promise A+规范的then
   * @param {Function} onFulfilled
   * @param {Function} onRejected
   */
  then(onFulfilled, onRejected) {
    return new MyPromise((resolve, reject) => {
      this._pushHandler(onFulfilled, FULFILLED, resolve, reject);
      this._pushHandler(onRejected, REJECTED, resolve, reject);
      this._runHandlers(); // 执行队列
    });
  }
​
  /**
   * 仅处理失败的场景
   * @param {Function} onRejected
   */
  catch(onRejected) {
    return this.then(null, onRejected);
  }
​
  /**
   * 无论成功还是失败都会执行回调
   * @param {Function} onSettled
   */
  finally(onSettled) {
    return this.then(
      (data) => {
        onSettled();
        return data;
      },
      (reason) => {
        onSettled();
        throw reason;
      }
    );
  }
​
  /**
   * 更改任务状态
   * @param {String} newState 新状态
   * @param {any} value 相关数据
   */
  _changeState(newState, value) {
    if (this._state !== PENDING) {
      // 目前状态已经更改
      return;
    }
    this._state = newState;
    this._value = value;
    this._runHandlers(); // 状态变化,执行队列
  }
​
  /**
   * 标记当前任务完成
   * @param {any} data 任务完成的相关数据
   */
  _resolve(data) {
    this._changeState(FULFILLED, data);
  }
​
  /**
   * 标记当前任务失败
   * @param {any} reason 任务失败的相关数据
   */
  _reject(reason) {
    this._changeState(REJECTED, reason);
  }
​
  /**
   * 返回一个已完成的Promise
   * 特殊情况:
   * 1. 传递的data本身就是ES6的Promise对象
   * 2. 传递的data是PromiseLike(Promise A+),返回新的Promise,状态和其保持一致即可
   * @param {any} data
   */
  static resolve(data) {
    if (data instanceof MyPromise) {
      return data;
    }
    return new MyPromise((resolve, reject) => {
      if (isPromise(data)) {
        data.then(resolve, reject);
      } else {
        resolve(data);
      }
    });
  }
​
  /**
   * 得到一个被拒绝的Promise
   * @param {any}} reason
   */
  static reject(reason) {
    return new MyPromise((resolve, reject) => {
      reject(reason);
    });
  }
​
  /**
   * 得到一个新的Promise
   * 该Promise的状态取决于proms的执行
   * proms是一个迭代器,包含多个Promise
   * 全部Promise成功,则返回的Promise成功,数据为所有Promise成功的数据,并且顺序是按照传入的顺序排列
   * 只要有一个Promise失败,则返回的Promise失败,原因是第一个失败的Promise的原因
   * @param {iterator} proms
   */
  static all(proms) {
    return new MyPromise((resolve, reject) => {
      try {
        const results = [];
        let count = 0; // Promise的总数
        let fulfilledCount = 0; // 已完成的数量
        for (const p of proms) {
          let i = count;
          count++;
          MyPromise.resolve(p).then((data) => {
            fulfilledCount++;
            results[i] = data;
            if (fulfilledCount === count) {
              // 当前是最后一个Promise完成了
              resolve(results);
            }
          }, reject);
        }
        if (count === 0) {
          resolve(results);
        }
      } catch (error) {
        reject(error);
        console.error(error);
      }
    });
  }
​
  /**
   * 等待所有的Promise有结果之后
   * 该方法返回的Promise完成
   * 并且按照顺序将所有结果汇总
   * @param {iterator} proms
   */
  static allSettled(proms) {
    const ps = [];
    for (const p of proms) {
      ps.push(
        MyPromise.resolve(p).then(
          (value) => ({
            status: FULFILLED,
            value,
          }),
          (reason) => ({
            status: REJECTED,
            reason,
          })
        )
      );
    }
    return MyPromise.all(ps);
  }
​
  /**
   * 返回的Promise与第一个有结果的一致
   * @param {iterator} proms
   */
  static race(proms) {
    return new MyPromise((resolve, reject) => {
      for (const p of proms) {
        MyPromise.resolve(p).then(resolve, reject);
      }
    });
  }
}
相关推荐
崔庆才丨静觅12 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅13 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅13 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment13 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅13 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊13 小时前
jwt介绍
前端
爱敲代码的小鱼14 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax
Cobyte14 小时前
AI全栈实战:使用 Python+LangChain+Vue3 构建一个 LLM 聊天应用
前端·后端·aigc
NEXT0614 小时前
前端算法:从 O(n²) 到 O(n),列表转树的极致优化
前端·数据结构·算法
剪刀石头布啊14 小时前
生成随机数,Math.random的使用
前端