【前端 JavaScript 高级】06 - Promise的入门及自定义Promise

第 1 章 Promise的理解和使用

1.1 Promise 是什么?

1.1.1 理解
抽象表达

Promise 是一门新的技术(ES6规范);
PromiseJS 中进行异步编程的新解决方案,备注:旧方案是单纯使用回调函数.
异步编程: fs文件操作、数据库操作、ajax、定时器。

具体表达

从语法上来说: Promise 是一个构造函数;
从功能上来说: Promise 对象用来封装一个异步操作并可以获取其成功/失败的结果值。

1.1.2 promise 的状态改变
  1. pending 变为 resolved;

  2. pending 变为 rejected;

  3. Promise初始状态是pending

说明: 只有这 2 种, 且一个 promise 对象只能改变一次,无论变为成功还是失败, 都会有一个结果数据成功的结果数据一般称为 value, 失败的结果数据一般称为 reason

Promise对象的两个属性
PromiseState : Promise 的状态 是实例对象中的一个属性 可能的值有:
  1. pending(未决定的);

  2. resolvedfullfilled 成功的);

  3. rejectrejected失败的);

状态的改变只会从 pending => fullfilled 或者 pending => rejected

PromiseResult 是实例对象中的另一个属性 保存着对象【成功/失败】的结果 保存异步任务成功或者失败的结果
  1. resolve

  2. reject

1.1.3 Promise初体验 : 抽奖案例
  1. 页面上有一个按钮当点击按钮之后1秒 ,显示是否中奖,不管中没中奖都给出相应的提示信息。
/**
   * 生成随机数
   * */
  function rand(m, n) {
    return Math.ceil(Math.random() * (n - m + 1)) + m - 1;
  }

  /**
   * 1. 点击按钮,1s后显示是否 中奖(30%概率中奖)
   *    1.1 如果中奖弹出恭喜恭喜,奖品...
   *    1.2 如果未中奖弹出,再接再厉
   */

    // 1. 传统回调函数的方式实现
  let btn = document.querySelector('#btn');
  /*btn.addEventListener('click', () => {
    setTimeout(function () {
      let n = rand(1, 100);// 获取 1~100的随机数
      if (n <= 30) {
        alert('恭喜恭喜,您中奖了!');
      } else {
        alert('很遗憾,请再来一次!');
      }
    }, 1000);
  });*/


  // 2. Promise 形式实现
  /**
   * resolve 解决 函数类型的数据
   *
   * reject 拒绝 函数类型的数据
   */
  btn.addEventListener('click', () => {
    let p = new Promise((resolve, reject) => {
      setTimeout(function () {
        let n = rand(1, 100);// 获取 1~100的随机数
        if (n <= 30) {
          resolve(n);
        } else {
          reject(n);
        }
      }, 1000);
    });

    /**
     * 1. 需求,将中奖的数字显示出来
     */
    p.then(value => {
        alert('恭喜恭喜,您中奖了! 将号码是 => ' + value);
      },
      reason => {
        alert('很遗憾,请再来一次!');
      })

  });
1.1.4 promise 的基本流程
Promise的基本执行流程

1.2 为什么要用 Promise?

1.2.1 指定回调函数的方式更加灵活
  1. 旧的: 必须在启动异步任务前指定。

  2. promise: 启动异步任务 => 返回 promie对象 => 给 promise对象绑定回调函数(甚至可以在异步任务结束后指定/多个)。

1.2.2 支持链式调用, 可以解决回调地狱问题
1. 什么是回调地狱?

答:回调函数嵌套调用, 外部回调函数异步执行的结果是嵌套的回调执行的条件。

2. 回调地狱的缺点?

不便于阅读
不便于异常处理

3. 解决方案?

promise 链式调用

4. 终极解决方案?

async/await

1.2.3 Promise封装 Ajax请求,点击按钮发送请求



  
  
  Promise封装ajax请求
  



1.2.4 Promise封装ajax请求,传递参数返回一个Promise对象



  
  
  Promise封装ajax请求






1.3 如何使用Promise

1.3.1 API
Promise 构造函数: Promise (excutor) {};
  1. executor 函数: 执行器 (resolve, reject) => {}

  2. resolve 函数: 内部定义成功时我们调用的函数 value => {}

  3. reject 函数: 内部定义失败时我们调用的函数reason => {}说明: executor 会在 Promise 内部立即同步调用,异步操作在执行器中执行。

/**
   * Promise 构造函数的 参数就是一个执行器,执行器函数是在Promise中同步调用的
   * @type {Promise}
   */
  let p = new Promise((resolve, reject) => {
    // 此函数内部的代码是同步执行的
    console.log(11); // 第一打印
    // 修改Promise对象的状态
    reject('error');
  });

  console.log(22); // 第二打印

  /**
   *  都是函数类型的参数
   */
  p.then(value => {

    },
    reason => {

    });

  /**
   * catch只能指定失败的回调
   */
  p.catch(reason => {
    console.log(reason);
  });
Promise.prototype.then 方法: (onResolved, onRejected) => {}
  1. onResolved 函数: 成功的回调函数(value) => {};

  2. onRejected 函数: 失败的回调函数 (reason) => {};

说明: 指定用于得到成功 value 的成功回调和用于得到失败 reason 的失败回调返回一个新的 promise 对象

Promise.prototype.catch 方法: (onRejected) => {}
  1. onRejected 函数: 失败的回调函数(reason) => {}

说明: then()的语法糖, 相当于: then(undefined, onRejected)

Promise.resolve 方法: (value) => {}
  1. value : 成功的数据或 promise 对象。

说明: 返回一个成功/失败的 promise 对象。

 /**
   * Promise对象的resolve方法
   *  1. 接收一个参数返回一个成功和失败的Promise对象
   *  2. 如果传递的参数为非Promise类型的对象,则返回的结果为成功的Promise对象
   *  3. 如果传递的参数是一个Promise对象,则参数的结果决定了 resolve的结果
   */
  let p1 = Promise.resolve('成功');
  console.log(p1);

  /**
   * 传递的参数是一个Promise对象 返回是成功
   * @type {Promise}
   */
  let p2 = Promise.resolve(new Promise((resolve, reject) => {
    resolve('OK');
  }));
  console.log(p2);

  /**
   * 传递的参数是一个Promise对象 返回的是失败
   * @type {Promise}
   */
  let p3 = Promise.resolve(new Promise((resolve, reject) => {
    reject('Error');
  }));
  console.log(p3);

  p3.catch(reason => {
    console.log(reason); // Error 对处理失败进行处理
  });
Promise.reject 方法: (reason) => {}
  1. reason: 失败的原因。

说明: 返回一个失败的 promise 对象。

/**
   * Promise中的reject方法
   *  1. reason:失败的原因
   *  2. 如果传递的是一个非Promise对象,其结果是一个失败的Promise
   *  3. 如果传递的是一个Promise,不管是返回成功的值还是失败的值其处理结果都是失败的Promise
   */
  let p1 = Promise.reject('失败!');
  console.log(p1);

  let p2 = Promise.reject(new Promise((resolve, reject) => {
    resolve('成功!');
  }));
  console.log(p2);

  let p3 = Promise.reject(new Promise((resolve, reject) => {
    reject('Error');
  }));
  console.log(p3);
Promise.all 方法: (promises) => {}
  1. promises: 包含 n 个 promise 的数组。

说明: 返回一个新的 promise, 只有所有的 promise 都成功才成功, 只要有一个失败了就直接失败。

  let p1 = new Promise((resolve, reject) => {
    resolve('OK!');
  });
  let p2 = Promise.resolve('Success!');
  let p3 = Promise.resolve('resolve!');
  let result = Promise.all([p1, p2, p3]); // 只有三个同时返回成功才会成功,否则将返回失败
  console.log(result);
Promise.race 方法: (promises) => {}
  1. promises: 包含 n 个 promise 的数组。

说明: 返回一个新的 promise, 第一个完成的 promise 的结果状态就是最终的结果状态。

/**
   * 返回一个新的Promise,第一个完成的Promise的结果状态就是最终的状态
   */
  let p1 = new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('OK!');
    }, 1000);
  });
  let p2 = Promise.resolve('Success!');
  let p3 = Promise.resolve('resolve!');
  let result = Promise.race([p1, p2, p3]); // 只有三个同时返回成功才会成功,否则将返回失败
  console.log(result);
Promise 封装读取文件函数
/**
 * 封装一个函数 mineReadFile读取文件内容
 * 参数: path文件路径
 * 返回: Promise对象
 */
const fs = require('fs');

function mineReadFile(path) {
  return new Promise((resolve, reject) => {
    fs.readFile(path, (err, data) => {
      if (err) reject(err);
      resolve(data);
    });
  });
}

mineReadFile('../resources/context.txt').then(value => {
    console.log(value.toString());
  },
  reason => {
    console.log(reason);
  });
const fs = require('fs');

/**
 * 1. 使用传统的方式
 */
fs.readFile('../resources/context.txt', (err, data) => {
  if (err) throw new Error(err);
  console.log(data.toString());
});

// 2. 使用Promise的方式
let p = new Promise((resolve, reject) => {
  fs.readFile('../resources/context.txt', (err, data) => {
    if (err) reject(err);
    resolve(data.toString());
  });
});

p.then(value => {
    console.log('------------------------------------------------------------------------');
    console.log(value.toString());
  },
  reason => {
    console.log(reason);
  });
util.promisify简化Promise
  1. util.promisifyNodeJSAPI 。 参考网址
/**
 * 传入一个遵循常见的错误优先的回调风格的函数(即以(err,value)  => 回调作为最后一个参数),并返回一个Promise的版本
 */

// 引入util 模块
const util = require('util'); // 引入util这个模块

// 引入fs模块
const fs = require('fs');

// 返回一个新函数
/**
 * util.promisify
 */
let mineReadFile = util.promisify(fs.readFile);

mineReadFile('../resources/context.txt').then(value => {
  console.log(value.toString());
});
1.3.2 promise 的几个关键问题
如何改变 promise 的状态?
  1. 开始Promise对象的初始化状态是 pending

  2. resolve(value): 如果当前是 pending就会变为resolved

  3. reject(reason): 如果当前是 pending 就会变为 rejected

  4. 抛出异常: 如果当前是 pending 就会变为 rejected

 /**
   * 代码中如何修改Promise对象的状态
   */
  let p = new Promise((resolve,reject) => {
    // 1. 调用resolve函数 将对象的状态 从 pending 修改为 fullfilled
    // resolve('OK');
    // 2. 调用reject函数 pending => rejected
    // reject('Error');
    // 3. 抛出异常
    throw new Error('出问题了!');
  });
一个 promise 指定多个成功/失败回调函数, 都会调用吗?
  1. promise 改变为对应状态时都会调用。
  /**
   * 为一个Promise对象指定多个回调,当他们的状态改变的时候都会调用,如果一直是pending状态的话就不会调用其回调
   */
  let p = new Promise((resolve, reject) => {
    resolve('OK');
  });

  p.then(value => {
    console.log(value);
  });

  p.then(value => {
    alert(value);
  });
改变 promise 状态和指定回调函数谁先谁后?
  1. 都有可能, 正常情况下是先指定回调再改变状态, 但也可以先改状态再指定回调。

  2. 如何先改状态再指定回调?

在执行器中直接调用 resolve()/reject()
延迟更长时间才调用 then()

  1. 什么时候才能得到数据?

如果先指定的回调, 那当状态发生改变时, 回调函数就会调用, 得到数据。
如果先改变的状态, 那当指定回调时, 回调函数就会调用, 得到数据。

promise.then()返回的新 promise 的结果状态由什么决定?
  1. 简单表达: 由 then()指定的回调函数执行的结果决定。

  2. 详细表达:

如果抛出异常, 新 promise 变为 rejected, reason 为抛出的异常。
如果返回的是非 promise 的任意值, 新 promise 变为 resolved, value 为返回的值。
如果返回的是另一个新 promise, 此 promise 的结果就会成为新 promise 的结果。

promise 如何串连多个操作任务
  1. promise 的 then()返回一个新的 promise, 可以开成 then()的链式调用。

  2. 通过 then 的链式调用串连多个同步/异步任务。

promise 异常传透?
  1. 当使用 promise 的 then 链式调用时, 可以在最后指定失败的回调。

  2. 前面任何操作出了异常, 都会传到最后失败的回调中处理。

中断 promise 链?
  1. 当使用 promise 的 then 链式调用时, 在中间中断, 不再调用后面的回调函数。

  2. 办法: 在回调函数中返回一个 pendding 状态的 promise 对象。


第 2 章 自定义(手写) Promise

2.1 函数版本

/**
 * 执行多个回调的实现
 * @param executor
 * @constructor
 */
function Promise(executor) {

  // 3. 添加属性
  this.PromiseState = 'pending';
  this.PromiseResult = null;
  // 声明一个属性
  this.callBacks = [];

  // 预先保存this的值
  const _this = this;

  function resolve(data) {
    // 修改对象的状态 PromiseState 属于实例对象的属性
    // 设置结果值 PromiseResult 属于实例对象的属性
    // 对象状态只允许修改一次
    if (_this.PromiseState !== 'pending') return;
    _this.PromiseState = 'fullfilled';
    _this.PromiseResult = data;
    // 在异步任务的时候,调用回调函数的时机
    // 循环遍历执行函数
    setTimeout(() => {
      _this.callBacks.forEach(item => {
        item.onResolved(data);
      });
    });
  }

  function reject(data) {
    // 对象状态只允许修改一次
    if (_this.PromiseState !== 'pending') return;
    _this.PromiseState = 'rejected';
    _this.PromiseResult = data;
    // 在异步任务的时候,调用回调函数的时机
    /**
     * 循环遍历执行多个函数
     */
    setTimeout(() => {
      _this.callBacks.forEach(item => {
        item.onRejected(data);
      });
    });
  }

  // 执行器函数式同步调用的
  // 调用执行器函数
  try {
    executor(resolve, reject);
  } catch (e) {
    reject(e);
  }
}

// 1. 添加then方法
Promise.prototype.then = function (onResolved, onRejected) {
  // 保存this的值
  const _this = this;
  // 判断回调函数的参数 是否为一个函数类型
  if (typeof onRejected !== 'function') {
    /**
     * 实现异常穿透的原理
     * @param reason
     */
    onRejected = reason => {
      throw reason;
    }
  }

  /**
   * Promise实现值传递
   */
  if (typeof onResolved !== 'function') {
    onResolved = value => value;
  }

  return new Promise((resolve, reject) => {

    function callBack(type) {
      /**
       * 当出现异常的时候
       */
      try {
        // 获取回调函数的执行结果
        let result = type(_this.PromiseResult);
        if (result instanceof Promise) {
          // 如果是Promise类型的对象
          result.then(value => {
              resolve(value);
            },
            reason => {
              reject(reason);
            })
        } else {
          // 返回非Promise对象的时候状态为成功
          resolve(result);
        }
      } catch (e) {
        reject(e);
      }
    }

    // 调用回调函数 成功失败的状态是 由PromiseState决定
    if (this.PromiseState === 'fullfilled') {
      setTimeout(() => {
        callBack(onResolved);
      });
    }

    if (this.PromiseState === 'rejected') {
      setTimeout(() => {
        callBack(onRejected);
      });
    }

    // 判断 pending的状态
    if (this.PromiseState === 'pending') {
      // 回调的执行时机?
      // 保存回调函数 重要 重要 重要
      // 8. 实现执行多个回调
      this.callBacks.push({
        onResolved: function () {
          callBack(onResolved);
        },
        onRejected: function () {
          callBack(onResolved);
        }
      });
    }
  });
};


// 12. 添加catch方法
Promise.prototype.catch = function (onRejected) {
  /**
   * 直接调用then方法
   */
  return this.then(undefined, onRejected);
};

// 13. 添加Promise.resolve方法
/**
 * Promise对象的 resolve 方法
 * @param value
 * @returns {Promise}
 */
Promise.resolve = function (value) {
  return new Promise((resolve, reject) => {
    // 判断value 的类型
    if (value instanceof Promise) {
      value.then(value => {
        resolve(value);
      }, reason => {
        reject(reason);
      });
    } else {
      resolve(value);
    }
  });
};

// 14. 添加 reject 方法
/**
 * 为Promise对象添加 reject 方法
 * @param reason
 * @returns {Promise}
 */
Promise.reject = function (reason) {
  return new Promise((resolve, reject) => {
    reject(reason);
  });
};

// 15. 添加all方法

/**
 * 为Promise对象添加 all 方法
 * @param promises
 * @returns {Promise}
 */
Promise.all = function (promises) {
  // 定义一个计数变量 计算当前Promise成功的个数
  let count = 0;
  let arr = [];
  return new Promise((resolve, reject) => {
    // 遍历 promises
    for (let i = 0; i < promises.length; i++) {
      promises[i].then(value => {
          // 得知对象的状态是成功
          // 状态成功就会走到该函数中 进行count++  操作
          count++;
          // 将当前promise对象成功的结果存到一个数组中保持原有的顺序
          arr[i] = value;
          // 判断
          if (count === promises.length) {
            // 修改此时Promise的状态
            resolve(arr);
          }
        },
        reason => {
          reject(reason);
        });
    }
  });
};


// 16 . race 方法 接受一个参数 Promise 对象数组 其结果由数组中最先改变状态的那个值决定

/**
 * Promise 对象的 race 方法
 * @param promises
 * @returns {Promise}
 */
Promise.race = function (promises) {
  return new Promise((resolve, reject) => {
    for (let i = 0; i < promises.length; i++) {
      promises[i].then(
        value => {
          resolve(value);
        },
        reason => {
          reject(reason);
        }
      );
    }
  });
};

// 17. 使用 setTimeOut 包裹代码将其转换为异步执行的代码

2.2 Class版本

class Promise {

  /**
   * 执行多个回调的实现
   * @param executor
   * @constructor
   */
  constructor(executor) {
    // 3. 添加属性
    this.PromiseState = 'pending';
    this.PromiseResult = null;
    // 声明一个属性
    this.callBacks = [];

    // 预先保存this的值
    const _this = this;

    function resolve(data) {
      // 修改对象的状态 PromiseState 属于实例对象的属性
      // 设置结果值 PromiseResult 属于实例对象的属性
      // 对象状态只允许修改一次
      if (_this.PromiseState !== 'pending') return;
      _this.PromiseState = 'fullfilled';
      _this.PromiseResult = data;
      // 在异步任务的时候,调用回调函数的时机
      // 循环遍历执行函数
      setTimeout(() => {
        _this.callBacks.forEach(item => {
          item.onResolved(data);
        });
      });
    }

    function reject(data) {
      // 对象状态只允许修改一次
      if (_this.PromiseState !== 'pending') return;
      _this.PromiseState = 'rejected';
      _this.PromiseResult = data;
      // 在异步任务的时候,调用回调函数的时机
      /**
       * 循环遍历执行多个函数
       */
      setTimeout(() => {
        _this.callBacks.forEach(item => {
          item.onRejected(data);
        });
      });
    }

    // 执行器函数式同步调用的
    // 调用执行器函数
    try {
      executor(resolve, reject);
    } catch (e) {
      reject(e);
    }
  }

  // then 方法
  then(onResolved, onRejected) {
    // 保存this的值
    const _this = this;
    // 判断回调函数的参数 是否为一个函数类型
    if (typeof onRejected !== 'function') {
      /**
       * 实现异常穿透的原理
       * @param reason
       */
      onRejected = reason => {
        throw reason;
      }
    }

    /**
     * Promise实现值传递
     */
    if (typeof onResolved !== 'function') {
      onResolved = value => value;
    }

    return new Promise((resolve, reject) => {

      function callBack(type) {
        /**
         * 当出现异常的时候
         */
        try {
          // 获取回调函数的执行结果
          let result = type(_this.PromiseResult);
          if (result instanceof Promise) {
            // 如果是Promise类型的对象
            result.then(value => {
                resolve(value);
              },
              reason => {
                reject(reason);
              })
          } else {
            // 返回非Promise对象的时候状态为成功
            resolve(result);
          }
        } catch (e) {
          reject(e);
        }
      }

      // 调用回调函数 成功失败的状态是 由PromiseState决定
      if (this.PromiseState === 'fullfilled') {
        setTimeout(() => {
          callBack(onResolved);
        });
      }

      if (this.PromiseState === 'rejected') {
        setTimeout(() => {
          callBack(onRejected);
        });
      }

      // 判断 pending的状态
      if (this.PromiseState === 'pending') {
        // 回调的执行时机?
        // 保存回调函数 重要 重要 重要
        // 8. 实现执行多个回调
        this.callBacks.push({
          onResolved: function () {
            callBack(onResolved);
          },
          onRejected: function () {
            callBack(onResolved);
          }
        });
      }
    });
  }

  // catch 方法
  catch(onRejected) {
    /**
     * 直接调用then方法
     */
    return this.then(undefined, onRejected);
  }


  /**
   * Promise对象的 resolve 方法
   * @param value
   * @returns {Promise}
   */
  static resolve(value) {
    return new Promise((resolve, reject) => {
      // 判断value 的类型
      if (value instanceof Promise) {
        value.then(value => {
          resolve(value);
        }, reason => {
          reject(reason);
        });
      } else {
        resolve(value);
      }
    });
  };


  /**
   * 为Promise对象添加 reject 方法
   * @param reason
   * @returns {Promise}
   */
  static reject(reason) {
    return new Promise((resolve, reject) => {
      reject(reason);
    });
  };


  /**
   * 为Promise对象添加 all 方法
   * @param promises
   * @returns {Promise}
   */
  static all(promises) {
    // 定义一个计数变量 计算当前Promise成功的个数
    let count = 0;
    let arr = [];
    return new Promise((resolve, reject) => {
      // 遍历 promises
      for (let i = 0; i < promises.length; i++) {
        promises[i].then(value => {
            // 得知对象的状态是成功
            // 状态成功就会走到该函数中 进行count++  操作
            count++;
            // 将当前promise对象成功的结果存到一个数组中保持原有的顺序
            arr[i] = value;
            // 判断
            if (count === promises.length) {
              // 修改此时Promise的状态
              resolve(arr);
            }
          },
          reason => {
            reject(reason);
          });
      }
    });
  };


  /**
   * Promise 对象的 race 方法
   * @param promises
   * @returns {Promise}
   */
  static race(promises) {
    return new Promise((resolve, reject) => {
      for (let i = 0; i < promises.length; i++) {
        promises[i].then(
          value => {
            resolve(value);
          },
          reason => {
            reject(reason);
          }
        );
      }
    });
  };
}

你可能感兴趣的:(【前端 JavaScript 高级】06 - Promise的入门及自定义Promise)