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);
      }
    });
  }
}
相关推荐
小镇程序员6 分钟前
vue2 src_Todolist全局总线事件版本
前端·javascript·vue.js
野槐8 分钟前
前端图像处理(一)
前端
程序猿阿伟15 分钟前
《智能指针频繁创建销毁:程序性能的“隐形杀手”》
java·开发语言·前端
疯狂的沙粒17 分钟前
对 TypeScript 中函数如何更好的理解及使用?与 JavaScript 函数有哪些区别?
前端·javascript·typescript
瑞雨溪26 分钟前
AJAX的基本使用
前端·javascript·ajax
力透键背28 分钟前
display: none和visibility: hidden的区别
开发语言·前端·javascript
程楠楠&M39 分钟前
node.js第三方Express 框架
前端·javascript·node.js·express
weiabc39 分钟前
学习electron
javascript·学习·electron
盛夏绽放1 小时前
Node.js 和 Socket.IO 实现实时通信
前端·后端·websocket·node.js
想自律的露西西★1 小时前
用el-scrollbar实现滚动条,拖动滚动条可以滚动,但是通过鼠标滑轮却无效
前端·javascript·css·vue.js·elementui·前端框架·html5