Promise 进阶、await 和 async

Promise 进阶


上一节:Promise 详细说明

1. async 和 await

async 函数、await 方法是 ES7 提出的基于 Promise 的解决异步的最终方案

1.1 async 函数

注:任意一个函数都可以被声明成一个 async 函数。

async 函数内部可以添加任意语句来执行,主要目的是为了得到一个 Promise 对象的状态及结果。

函数内部返回结果主要有三种情况:

  1. 返回非 Promise 对象的数据

    async function main() {
      return 111;
    }
    let result = main();
    console.log(result); // Promise {: 111}
    
  2. 返回 Promise 对象

    返回的 Promise 对象的状态及结果值,直接影响道产生结果的 Promise 对象的状态及结果值

    async function main() {
      return new Promise((resolve, reject) => {
        resolve("ok");
        // reject("err");
      });
    }
    let result = main();
    console.log(result); // Promise {: "ok"}
    
  3. 抛出异常

    async function main() {
      throw "出错了";
      // throw new Error("出错了");
    }
    let result = main();
    console.log(result); // Promise {: '出错了'}
    

1.2 await 表达式

async 函数结合 await 表达式注意点:

  1. async 函数中不一定要包含 await 表达式;
  2. 有 await 表达式一定是 async 函数;
  3. await 相当于 then,可以直接拿到成功的 Promise 对象的结果值

await 表达式后所跟内容一般有三种情况:

  1. await 后非 Promise 对象,该值会被直接返回

    async function main() {
      let rs = await 100;
      console.log(rs); // 100
    }
    main();
    
  2. await 后为成功的 Promise 对象,成功的结果值会被直接返回

    async function main() {
      let rs = await new Promise((resolve, reject)=>{
        resolve('ok')
      });
      console.log(rs); // ok
      // let rs = await Promise.resolve('okk');
      // console.log(rs); // okk
    }
    main();
    
  3. await 后为失败的 Promise 对象,会抛出异常,可以使用 try...catch 捕获

    async function main() {
      try {
        // let rs = await new Promise((resolve, reject)=>{
        //   reject('error')
        // })
        let rs = await Promise.reject("error");
        console.log(rs);
      } catch (e) {
        console.log(e); // error
      }
    }
    main();
    

await 表达式执行顺序:

async function main() {
  console.log(1111);
  let result = await Promise.resolve('ok');
  console.log(result);
  console.log(2222);
  console.log(3333);
}
main();
console.log(5555);
console.log(6666);

// 输出结果:
// 1111
// 5555
// 6666
// ok
// 2222
// 3333

结论:先同步后异步

2. 宏队列与微队列

JS 中用来存储待执行的回调函数的队列中包含了两个特定的队列:

  1. 宏队列:用来保存执行的宏任务(回调),比如:定时器、DOM 事件、Ajax 请求等;
  2. 微队列:用来保存执行的微任务(回调),比如:Promise 等。

JS 执行的时候会区分两个队列

  1. 首先 JS 引擎会必须先将所有的同步代码执行完毕;
  2. 每次准备取出第一个宏任务执行之前,都需要将所有微任务依次取出执行。

执行顺序大致为:同步代码 -> 微队列 -> 宏队列 (同、微、宏

setTimeout(() => {
  console.log(1111);
}, 0);
new Promise((resolve, reject) => {
  // reject();
  resolve();
  console.log(2222);
}).then((value) => {
  console.log(3333);
});
console.log(4444);
// 同:2222 4444
// 微:3333
// 宏:1111

3. Promise 自定义基础结构的搭建

3.1 Promise 的基本结构

  1. Promise 是一个构造函数,用来生成 Promise 实例

    语法:new Promise(executor);

    在堆内存中开辟一块空间,分配其大小以及设置地址值

    •     堆内存:主要存储的是引用类型的数据(数组、对象、函数)
      
    •     栈内存:主要存储的是基本类型的数据以及引用数据类型的地址值
      
  2. Promise 构造函数是需要接收一个实际参数,该参数是一个函数(执行器函数 executor)

  3. executor 函数接收两个参数 resolve 和 reject,分别用于将 Promise 的状态从 pending 变为 fulfilled 或 rejected

// 立即调用函数的好处:可以避免对外部的变量造成污染。
(function(window) {
  function Promise(executor) {
    executor();
  }
  window.Promise = Promise; // 全局添加
})(window);
new Promise((resolve, reject) => {
  console.log("这是我的执行器函数",resolve,reject)
});
console.log(Promise);

3.2 Promsie 的两个实例属性

  1. 任意一个构造函数的实例化对象上都有一个默认的属性,这个属性叫做隐式属性(__proto__
  2. Promise 实例化对象身上还有两个属性,一个是 PromiseState(记录当前 Promise 的状态),另一个是 PromiseResult(记录当前 Promise 的结果)

只要是为构造函数的实例化对象添加属性、方法,那么直接在构造函数中 this 对象的地址引用上直接添加。

(function (window) {
  function Promise(executor) {
    // 为 Promise 构造函数所生成的实例化对象添加状态和结果属性
    this.PromiseState = "pending";
    this.PromiseResult = undefined;

    executor();
  }
  window.Promise = Promise;
})(window);

3.3 Promise 状态修改

  1. 通过调用 resolve 方法,可以将 Promise 的状态从 pending 变为 fulfilled,并将执行结果传递给该 Promise 的回调函数;
  2. 通过调用 reject 方法,可以将 Promise 的状态从 pending 变为 rejected,并将错误信息传递给该 Promise 的回调函数;
  3. Promise 的实例化对象状态只能被修改一次
  4. 抛出异常时,Promise 的状态为 rejected,并将错误信息传递给该 Promise 的回调函数;
  5. executor 函数接收两个参数,分别是 resolve 和 reject,它们是两个函数,用于改变 Promise 的状态。

在 executor 函数中的两个参数均为函数,这时有可能会出现 this 指向问题,this 应与[上述](#3.2 Promsie 的两个实例属性)的 this 指向相同。

修改函数中 this 指向的三种方法:call、apply、bind

语法:

  • 函数名.call(this指向, 实参1, ...);
  • 函数名.apply(this指向, [实参1, ...]);

call 和 apply 的区别:函数中修改完 this 指向,实际参数传递的格式不同

call 和 apply 的共同点:当 this 指向被修改完成后,函数会立即执行

call 和 bind 的区别:call 会立即执行,而 bind 不会,且只有返回值,返回的是修改后的 this 指向的新函数。

(function (window) {
  function Promise(executor) {
    // 为 Promise 构造函数所生成的实例化对象添加状态和结果属性
    this.PromiseState = "pending";
    this.PromiseResult = undefined;

    executor(function () { }, function () { }); // 两个参数分别表示 resolve、reject
  }
  window.Promise = Promise;
})(window);

由于 executor 函数中代码重复率较高,因此需要将两个参数所对应代码进行分离

3.3.1 方法未分离
try {
  executor(
    function (argument) {
      // console.log(this);
      this.PromiseState = "fulfilled";
      this.PromiseResult = argument;
    }.bind(this),
    function (argument) {
      this.PromiseState = "rejected";
      this.PromiseResult = argument;
    }.bind(this)
  );
} catch (err) {
  this.PromiseState = "rejected";
  this.PromiseResult = err;
  console.error(err);
}
let p = new Promise((resolve, reject) => {
  // resolve(100);
  // reject("error");

  throw 'error'
  // throw new Error("error");
});
3.3.2 分离为箭头函数
// 定义 resolve 和 reject 方法
let _resolve = (argument) => {
  this.PromiseState = "fulfilled";
  this.PromiseResult = argument;
};
let _reject = (argument) => {
  this.PromiseState = "rejected";
  this.PromiseResult = argument;
};

try {
  executor(_resolve, _reject);
} catch (err) {
  this.PromiseState = "rejected";
  this.PromiseResult = err;
  console.error(err);
}

箭头函数自动继承外层函数的 this 指向

3.3.3 状态只能修改一次
let _resolve = (argument) => {
  // Promise 状态为 pending 时才可以更改状态
  if (this.PromiseState !== "pending") return
  this.PromiseState = "fulfilled";
  this.PromiseResult = argument;
};
let _reject = (argument) => {
  if (this.PromiseState !== "pending") return
  this.PromiseState = "rejected";
  this.PromiseResult = argument;
};

3.4 then 方法实现

then 方法接收两个参数,分别是在 Promise 成功和失败时的回调函数。

借助 Object.assign() 方法实现

3.3.1 成功、失败回调
function Promise(executor) { ... }

// 借助 Object.assign() 方法将 Promise 原型上的方法和属性添加到实例化对象上
Object.assign(Promise.prototype, {
  // onfulfilled 成功回调函数,onrejected 失败回调函数
  then(onfulfilled, onrejected) {
    if (this.PromiseState === "fulfilled") {
      onfulfilled(this.PromiseResult);
    } else if (this.PromiseState === "rejected") {
      onrejected(this.PromiseResult);
    }
  },
});
3.3.2 then 方法中的回调异步执行

这两个回调函数是在 Promise 的状态改变时被调用的,这个调用是异步的,因为它依赖于 Promise 的状态变化

Object.assign(Promise.prototype, {
  // onfulfilled 成功回调函数,onrejected 失败回调函数
  then(onfulfilled, onrejected) {
    if (this.PromiseState === "fulfilled") {
      // 使用定时器,将调用改为异步
      setTimeout(() => { onfulfilled(this.PromiseResult); });
    } else if (this.PromiseState === "rejected") {
      setTimeout(() => { onrejected(this.PromiseResult); });
    }
  },
});
3.3.3 返回内容

then 方法的[返回结果](#4.1 then 方法返回结果)是一个新的 Promise 实例化对象

  • 新的 Promise 实例化对象的状态取决于调用 then 方法对象的状态;

  • 如果没有返回值,则返回是一个成功的 Promise,结果值为 undefined;

  • 如果有返回值,则需要根据返回内容进行判断:

    • 返回非 Promise 对象,则返回成功的 Promise 实例化对象,结果值为返回值;
    • 返回 Promise 对象,则返回值和返回状态均由返回的 promise 对象的返回值和状态决定
    • 如果抛出异常,则返回一个失败的 Promise 对象。
Object.assign(Promise.prototype, {
  then(onfulfilled, onrejected) {
    // 调用 then 方法,返回一个新的 Promise 对象
    return new Promise((resolve, reject) => {
      if (this.PromiseState === "fulfilled") {
        setTimeout(() => {
          try {
            const value = onfulfilled(this.PromiseResult);
            // 判断 value 是否为 Promise 对象
            if (value instanceof Promise) {
              value.then(resolve, reject);
            } else {
              // 不是 Promise 对象,直接调用 resolve 方法
              resolve(value);
            }
          } catch (e) {
            // 如果有异常,则调用 reject 方法
            reject(e);
          }
        });
      } else if (this.PromiseState === "rejected") {
        setTimeout(() => {
          try {
            const value = onrejected(this.PromiseResult);
            if (value instanceof Promise) {
              value.then(resolve, reject);
            } else {
              resolve(value);
            }
          } catch (e) {
            reject(e);
          }
        });
      }
    });
  },
});
3.3.4 优化 then 方法代码
Object.assign(Promise.prototype, {
  then(onfulfilled, onrejected) {
    // 调用 then 方法,返回一个新的 Promise 对象
    return new Promise((resolve, reject) => {
      // 封装函数
      let _common = (callback) => {
        setTimeout(() => {
          try {
            const value = callback(this.PromiseResult);
            // 判断 value 是否为 Promise 对象
            if (value instanceof Promise) {
              value.then(resolve, reject);
            } else {
              // 不是 Promise 对象,直接调用 resolve 方法
              resolve(value);
            }
          } catch (e) {
            // 如果有异常,则调用 reject 方法
            reject(e);
          }
        });
      };

      if (this.PromiseState === "fulfilled") {
        _common(onfulfilled);
      } else if (this.PromiseState === "rejected") {
        _common(onrejected);
      }
    });
  },
});
3.3.5 增加成功、失败回调默认值
  1. then 方法如果省略了成功的回调函数,则默认成功的回调函数为 value => value
  2. then 方法如果省略了失败的回调函数,则默认失败的回调函数为 reason => { throw reason }
Object.assign(Promise.prototype, {
  then(onfulfilled, onrejected) {
    // 如果 onfulfilled 不是函数,则将其设置为默认值,即返回 Promise 对象的结果
    if (!(onfulfilled instanceof Function)) {
      onfulfilled = (value) => value;
    }
    if (!(onrejected instanceof Function)) {
      onrejected = (reason) => { throw reason };
    }

    // 调用 then 方法,返回一个新的 Promise 对象
    return new Promise((resolve, reject) => { ... });
  },
});
let p1 = new Promise((resolve, reject) => {
  resolve(100);
  // reject('error');
});
let p2 = p1.then();
console.log(p2);
3.3.6 执行器函数处理异步行为

如果 executor 执行器函数中执行的是异步程序,则将 then 方法的回调函数存入 callbacks 对象中,等待状态改变时执行对应的回调函数。

function Promise(executor) {
  // ...
  this.callbacks = {}; // 存放成功和失败对应的回调函数

  let _resolve = (argument) => {
    // ...
    if (this.callbacks.onfulfilled) {
      this.callbacks.onfulfilled(argument);
    }
  };
  let _reject = (argument) => {
    // ...
    if (this.callbacks.onrejected) {
      this.callbacks.onrejected(argument);
    }
  };

  // ...
}

Object.assign(Promise.prototype, {
  then(onfulfilled, onrejected) {
    // ...

    return new Promise((resolve, reject) => {
      let _common = (callback) => { ... };

      if (this.PromiseState === "fulfilled") {
        _common(onfulfilled);
      } else if (this.PromiseState === "rejected") {
        _common(onrejected);
      } else {
        // 异步程序处理
        this.callbacks = {
          onfulfilled: _common.bind(this, onfulfilled),
          onrejected: _common.bind(this, onrejected),
        };
      }
    });
  },
});
3.3.7 then 方法多次调用
const p1 = new Promise((resolve, reject) => {
  setTimeout(() => { resolve(100) }, 1000);
});
p1.then(
  (value) => { console.log("成功1", value); },
  (reason) => { console.log("失败1", reason); }
);
p1.then(
  (value) => { console.log("成功2", value); },
  (reason) => { console.log("失败2", reason); }
);
p1.then(
  (value) => { console.log("成功3", value); },
  (reason) => { console.log("失败3", reason); }
);
// ...
console.log(p1);

解决方案:将存储成功和失败的参数 callbacks 改为数组类型的数据。

function Promise(executor) {
  // ...
  this.callbacks = []; // 存放成功和失败对应的回调函数

  // 定义 resolve 和 reject 方法
  let _resolve = (argument) => {
    // ...
    // 调用成功回调函数
    this.callbacks.forEach((callback) => {
      callback.onfulfilled(argument);
    });
  };
  let _reject = (argument) => {
    // ...
    this.callbacks.forEach((callback) => {
      callback.onrejected(argument);
    });
  };

  // ...
}

Object.assign(Promise.prototype, {
  then(onfulfilled, onrejected) {
    // ...

    return new Promise((resolve, reject) => {
      let _common = (callback) => {  };

      if (this.PromiseState === "fulfilled") {
        _common(onfulfilled);
      } else if (this.PromiseState === "rejected") {
        _common(onrejected);
      } else {
        // 异步程序处理
        this.callbacks.push({
          onfulfilled: _common.bind(this, onfulfilled),
          onrejected: _common.bind(this, onrejected),
        });
      }
    });
  },
});

3.5 catch 方法

  1. catch 方法有返回值,返回的也是新的 Promise 对象;
  2. catch 方法的返回值和 then 方法的返回值类似

catch 方法是 then 方法的语法糖,用于处理 Promise 对象状态为 rejected 时调用的回调函数,与 then(null, onrejected) 等效。

let p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    // resolve(100);
    reject("error");
  });
});

let p2 = p1.catch((err) => {
  // console.log(err);
  // return err
  return new Promise((resolve, reject) => {
    resolve("okkk");
    // reject("errorrr");
  });
  // throw "异常";
});

console.log(p2);
Object.assign(Promise.prototype, {
  // onfulfilled 成功回调函数,onrejected 失败回调函数
  then(onfulfilled, onrejected) {  },

  catch(onrejected) {
    return this.then(null, onrejected); // 直接调用 then 方法即可
  },
});

3.6 then 方法的链式调用

在之前已经规定了 then 方法的返回值,因此无需任何的修改。

3.7 异常穿透问题

then 方法返回值

3.8 中断 Promise 链条

返回一个空的 Promise 对象,then 方法返回值

上面三个问题,都已经在规定 then 方法返回值时解决。

3.9 Promise 下独立的方法

3.9.1 resolve 方法

Promise.resolve() 方法可以进行传参,参数说明:

  1. 非 Promise 实例,则返回一个成功的 Promise 对象
  2. 是 Promise 实例,则返回该 Promise 实例
let p1 = Promise.resolve(1);
console.log(p1); // Promise {: 1}

let p2 = Promise.resolve(new Promise((resolve, reject) => { resolve(2); }));
console.log(p2); // Promise {: 2}

let p3 = Promise.resolve(new Promise((resolve, reject) => { reject("err"); }));
console.log(p3); // Promise {: "err"}
(function (window) {
  function Promise(executor) {  }

  Object.assign(Promise.prototype, {  });

  // Promise 构造函数中添加方法
  Promise.resolve = function (value) {
    // 如果 value 已经是 Promise 对象,则直接返回 value
    if (value instanceof Promise) {
      return value;
    } else {
      // 否则,返回一个新的 Promise 对象,状态为 fulfilled,结果为 value
      return new Promise((resolve) => { resolve(value); });
    }
  };

  // 全局添加 Promise
  window.Promise = Promise;
})(window);
3.9.2 reject 方法

Promise.reject() 方法不受参数影响,始终得到一个失败的 Promise 对象,并将参数作为该 Promise 对象的 PromiseResult 的值。

let p1 = Promise.reject(1);
console.log(p1); // Promise {: 1}

let p2 = Promise.reject(new Promise((resolve, reject) => { resolve(2); }));
console.log(p2); // Promise {: Promise} 里面嵌套的 Promise 是成功的

let p3 = Promise.reject(new Promise((resolve, reject) => { reject("err"); }));
console.log(p3); // Promise {: Promise} 里面嵌套的 Promise 是失败的
(function (window) {
  // ...

  // Promise 构造函数中添加方法
  Promise.resolve = function (value) {  };
  Promise.reject = function (reason) {
    return new Promise((resolve, reject) => {
      reject(reason);
    });
  };

  // 全局添加 Promise
  window.Promise = Promise;
})(window);
3.9.3 all 方法

Promise.all() 方法需要传递一个数组参数,数组中的元素都是 Promise 对象,返回结果是一个新的 Promise 对象。

  1. 如果所有的 Promise 对象的状态都是成功状态,则返回的 Promise 状态为成功,结果值为数组中每个 Promise 对象的结果值组成的数组。
  2. 如果有任何一个 Promise 对象的状态是失败状态,则返回的 Promise 状态为失败,失败原因为第一个失败的 Promise 对象的失败原因。
let p1 = new Promise((resolve, reject) => { resolve(100); });
let p2 = new Promise((resolve, reject) => {
  resolve(200);
  // reject(200);
});
let p3 = new Promise((resolve, reject) => {
  resolve(300);
  // reject(300);
});

const pArr = Promise.all([p1, p2, p3]);
console.log(pArr);
(function (window) {
  // ...

  // Promise 构造函数中添加方法
  Promise.resolve = function (value) {  };
  Promise.reject = function (reason) {  };
  Promise.all = function (PromiseArr) {
    let count = 0; // 定义一个计数器,统计成功的 Promise 个数
    // 定义一个数组,用于存放 Promise 对象
    let successArr = new Array(PromiseArr.length);

    return new Promise((resolve, reject) => {
      PromiseArr.forEach((item, i) => {
        item.then(
          (value) => {
            count++;
            successArr[i] = value;
            // 如果计数器等于 Promise 数组的长度,则证明所有的 Promise 均成功
            if (count === PromiseArr.length) { resolve(successArr); }
          },
          (reason) => { reject(reason); }
        );
      });
    });
  };

  // 全局添加 Promise
  window.Promise = Promise;
})(window);
3.9.4 race 方法

Promise.race() 方法需要传入一个 promise 数组,该方法返回一个promise对象,数组中的 Promise 谁先完成状态的改变,就以该 Promise 的状态和结果为最终状态和结果。

let p1 = new Promise((resolve, reject) => {
  setTimeout(() => { resolve(100); }, 50);
});
let p2 = new Promise((resolve, reject) => { reject(200); });
let p3 = new Promise((resolve, reject) => { resolve(300); });

const pArr = Promise.race([p3, p2, p1]);
console.log(pArr);
(function (window) {
  // ...

  // Promise 构造函数中添加方法
  Promise.resolve = function (value) {  };
  Promise.reject = function (reason) {  };
  Promise.all = function (PromiseArr) {  };
  Promise.race = function (PromiseArr) {
    return new Promise((resolve, reject) => {
      PromiseArr.forEach(item => {
        item.then(resolve, reject)
      })
    })
  };

  // 全局添加 Promise
  window.Promise = Promise;
})(window);

你可能感兴趣的:(Axios,前端,javascript,Promise)