高阶JavaScript笔记(六)

143 阅读35分钟

十、Promise详解

异步任务的处理

  • 在ES6出来之后,有很多关于Promise的讲解、文章,也有很多经典的书籍讲解Promise

    • 虽然等你学会Promise之后,会觉得Promise不过如此,但是在初次接触的时候都会觉得这个东西不好理解;
  • 那么这里我从一个实际的例子来作为切入点:

    • 我们调用一个函数,这个函数中发送网络请求(我们可以用定时器来模拟);
    • 如果发送网络请求成功了,那么告知调用者发送成功,并且将相关数据返回过去;
    • 如果发送网络请求失败了,那么告知调用者发送失败,并且告知错误信息;
/**
 * 这种回调的方式有很多的弊端:
 *  1> 如果是我们自己封装的requestData,那么我们在封装的时候必须要自己设计好callback名称, 并且使用好
 *  2> 如果我们使用的是别人封装的requestData或者一些第三方库, 那么我们必须去看别人的源码或者文档, 才知道它这个函数需要怎么去获取到结果
 */// request.js
function requestData(url, successCallback, failtureCallback) {
  // 模拟网络请求
  setTimeout(() => {
    // 拿到请求的结果
    // url传入的是coderwhy, 请求成功
    if (url === "coderwhy") {
      // 成功
      let names = ["abc", "cba", "nba"]
      successCallback(names)
    } else { // 否则请求失败
      // 失败
      let errMessage = "请求失败, url错误"
      failtureCallback(errMessage)
    }
  }, 3000);
}
​
// main.js
requestData("kobe", (res) => {
  console.log(res)
}, (err) => {
  console.log(err)
})
​
// 更规范/更好的方案 Promise承诺(规范好了所有的代码编写逻辑)
function requestData2() {
  return "承诺"
}
​
const chengnuo = requestData2()

Promise简介

什么是Promise呢?

  • 在上面的解决方案中,我们确确实实可以解决请求函数得到结果之后,获取到对应的回调,但是它存在两个主要的问题:

    • 第一,我们需要自己来设计回调函数、回调函数的名称、回调函数的使用等;
    • 第二,对于不同的人、不同的框架设计出来的方案是不同的,那么我们必须耐心去看别人的源码或者文档,以便可以理解它这个函数到底怎么用;
  • 我们来看一下Promise的API是怎么样的:

    • Promise是一个类,可以翻译成 承诺、许诺 、期约;

    • 当我们需要给予调用者一个承诺:待会儿我会给你回调数据时,就可以创建一个Promise的对象;

    • 在通过new创建Promise对象时,我们需要传入一个回调函数,我们称之为executor

      • 这个回调函数会被立即执行,并且给传入另外两个回调函数resolve、reject;
      • 当我们调用resolve回调函数时,会执行Promise对象的then方法传入的回调函数;
      • 当我们调用reject回调函数时,会执行Promise对象的catch方法传入的回调函数;

Promise代码结构:

// 传入的这个函数, 被称之为 executor
// > resolve: 回调函数, 在成功时, 回调resolve函数
// >reject: 回调函数, 在失败时, 回调reject函数
const promise = new Promise((resolve, reject) => {
  // console.log("promise传入的函数被执行了")
  // resolve()
  reject()
})
​
promise.then(() => {
​
})
​
promise.catch(() => {
​
})

上面Promise使用过程,我们可以将它划分成三个状态:

  • 待定(pending) : 初始状态,既没有被兑现,也没有被拒绝;

    • 当执行executor中的代码时,处于该状态;
  • 已兑现(fulfilled) : 意味着操作成功完成;

    • 执行了resolve时,处于该状态;
  • 已拒绝(rejected) : 意味着操作失败;

    • 执行了reject时,处于该状态;

用了Promise,我们就可以将之前的代码进行重构了:

// request.js
function requestData(url) {
  // 异步请求的代码会被放入到executor中
  return new Promise((resolve, reject) => {
    // 模拟网络请求
    setTimeout(() => {
      // 拿到请求的结果
      // url传入的是coderwhy, 请求成功
      if (url === "coderwhy") {
        // 成功
        let names = ["abc", "cba", "nba"];
        resolve(names);
      } else {
        // 否则请求失败
        // 失败
        let errMessage = "请求失败, url错误";
        reject(errMessage);
      }
    }, 3000);
  });
}
​
// main.js
const promise = requestData("coderwhy");
promise.then(
  (res) => {
    console.log("请求成功:", res);
  },
  (err) => {
    console.log("请求失败:", err);
  }
);

Executor

  • Executor是在创建Promise时需要传入的一个回调函数,这个回调函数会被立即执行,并且传入两个参数:

  • 通常我们会在Executor中确定我们的Promise状态:

    • 通过resolve,可以兑现(fulfilled)Promise的状态,我们也可以称之为已决议(resolved);
    • 通过reject,可以拒绝(reject)Promise的状态;
  • 这里需要注意:一旦状态被确定下来,Promise的状态会被锁死,该Promise的状态是不可更改的

    • 在我们调用resolve的时候,如果resolve传入的值本身不是一个Promise,那么会将该Promise的状态变成兑现(fulfilled);
    • 在之后我们去调用reject时,已经不会有任何的响应了(并不是这行代码不会执行,而是无法改变Promise状态);

resolve不同值的区别

  • 情况一:如果resolve传入一个普通的值或者对象,那么这个值会作为then回调的参数;
  • 情况二:如果resolve中传入的是另外一个Promise,那么这个新Promise会决定原Promise的状态:
  • 情况三:如果resolve中传入的是一个对象,并且这个对象有实现then方法,那么会执行该then方法,并且根据then方法的结果来决定Promise的状态:
// 完全等价于下面的代码
// 注意: Promise状态一旦确定下来, 那么就是不可更改的(锁定)
new Promise((resolve, reject) => {
  // pending状态: 待定/悬而未决的
  console.log("--------");
  reject(); // 处于rejected状态(已拒绝状态)
  resolve(); // 处于fulfilled状态(已敲定/兑现状态)
  console.log("++++++++++++");
}).then(
  (res) => {
    console.log("res:", res);
  },
  (err) => {
    console.log("err:", err);
  }
);

常见方法

then方法

  • then方法是Promise对象上的一个方法:它其实是放在Promise的原型上的 Promise.prototype.then

  • then方法接受两个参数:

    • fulfilled的回调函数:当状态变成fulfilled时会回调的函数;
    • reject的回调函数:当状态变成reject时会回调的函数;
promise.then(res => {
  console.log("res:", res);
}, err => {
  console.log("err:", err);
})
// 等价于
​
promise.then(res => {
  console.log("res:", res);
}).catch(err => {
  console.log("err:", err);
})
多次调用
  • 一个Promise的then方法是可以被多次调用的:

    • 每次调用我们都可以传入对应的fulfilled回调;
    • 当Promise的状态变成fulfilled的时候,这些回调函数都会被执行;
promise.then(res => {
  console.log("res1:", res)
})
​
promise.then(res => {
  console.log("res2:", res)
})
​
promise.then(res => {
  console.log("res3:", res)
})
返回值
  • Promise有三种状态,那么这个Promise处于什么状态呢?

    • 当then方法中的回调函数本身在执行的时候,那么它处于pending状态;

    • 当then方法中的回调函数返回一个结果时,那么它处于fulfilled状态,并且会将结果作为resolve的参数;

      • 情况一:返回一个普通的值;
      • 情况二:返回一个Promise;
      • 情况三:返回一个thenable值;
    • 当then方法抛出一个异常时,那么它处于reject状态;

// 2.then方法传入的 "回调函数: 可以有返回值
// then方法本身也是有返回值的, 它的返回值是Promise// 1> 如果我们返回的是一个普通值(数值/字符串/普通对象/undefined), 那么这个普通的值被作为一个新的Promise的resolve值
promise
  .then((res) => {
    return "aaaaaa";
  })
  .then((res) => {
    console.log("res:", res);
    return "bbbbbb";
  });
​
// 2> 如果我们返回的是一个Promise
promise.then(res => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(111111)
    }, 3000)
  })
}).then(res => {
  console.log("res:", res)
})
​
// 3> 如果返回的是一个对象, 并且该对象实现了thenable
promise
  .then((res) => {
    return {
      then: function (resolve, reject) {
        resolve(222222);
      },
    };
  })
  .then((res) => {
    console.log("res:", res);
  });

catch方法

  • catch方法也是Promise对象上的一个方法:它也是放在Promise的原型上的 Promise.prototype.catch

  • 一个Promise的catch方法是可以被多次调用的:

    • 每次调用我们都可以传入对应的reject回调;
    • 当Promise的状态变成reject的时候,这些回调函数都会被执行
promise.catch(err => {
  console.log("err:", err);
})
返回值

事实上catch方法也是会返回一个Promise对象的,所以catch方法后面我们可以继续调用then方法或者catch方法:

  • 但是第一个promise会返回一个undefined
  • 如果我们希望后续继续执行catch,那么需要抛出一个异常
promise
  .catch(err => {
    console.log("err1:", err);
    // throw new Error("error message")
  })
  .catch(err => {
    console.log("err2:", err);
  })
  .then(res => {
    console.log("res:", res);
  })
// err1: hahaha
// res: undefined

finally方法

  • finally是在ES9(ES2018)中新增的一个特性:表示无论Promise对象无论变成fulfilled还是reject状态,最终都会被执行的代码。
  • finally方法是不接收参数的,因为无论前面是fulfilled状态,还是reject状态,它都会执行。
promise
  .then((res) => {
    console.log("res:", res);
  })
  .catch((err) => {
    console.log("err:", err);
  })
  .finally(() => {
    console.log("finally code execute");
  });

resolve方法

  • 有时候我们已经有一个现成的内容了,希望将其转成Promise来使用,这个时候我们可以使用 Promise.resolve 方法来完成。
  • Promise.resolve的用法相当于new Promise,并且执行resolve操作:
// 类方法Promise.resolve
// 1.普通的值
const promise = Promise.resolve({ name: "why" })
// 相当于
const promise2 = new Promise((resolve, reject) => {
  resolve({ name: "why" })
})
​
// 2.传入Promise
const promise = Promise.resolve(new Promise((resolve, reject) => {
  resolve("11111")
}))
​
promise.then(res => {
  console.log("res:", res)
})
​
// 3.传入thenable对象
const promise = Promise.resolve((res) => {
  return {
    then: function (resolve, reject) {
      resolve(222222);
    },
  };
})
  • resolve参数的形态:

    • 情况一:参数是一个普通的值或者对象
    • 情况二:参数本身是Promise
    • 情况三:参数是一个thenable

reject方法

  • reject方法类似于resolve方法,只是会将Promise对象的状态设置为reject状态。
  • Promise.reject的用法相当于new Promise,只是会调用reject:
  • Promise.reject传入的参数无论是什么形态,都会直接作为reject状态的参数传递到catch的
// 注意: 无论传入什么值都是一样的
const promise = Promise.reject(new Promise(() => {}));
​
promise
  .then((res) => {
    console.log("res:", res);
  })
  .catch((err) => {
    console.log("err:", err);
  });
​

all方法

另外一个类方法是Promise.all:

  • 它的作用是将多个Promise包裹在一起形成一个新的Promise;

  • 新的Promise状态由包裹的所有Promise共同决定:

    • 当所有的Promise状态变成fulfilled状态时,新的Promise状态为fulfilled,并且会将所有Promise的返回值组成一个数组;
    • 当有一个Promise状态为reject时,新的Promise状态为reject,并且会将第一个reject的返回值作为参数;
// 创建多个Promise
const p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(11111);
  }, 1000);
});
​
const p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject(22222);
  }, 2000);
});
​
const p3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(33333);
  }, 3000);
});
​
// 需求: 所有的Promise都变成fulfilled时, 再拿到结果
// 意外: 在拿到所有结果之前, 有一个promise变成了rejected, 那么整个promise是rejected
Promise.all([p2, p1, p3, "aaaa"])
  .then((res) => {
    console.log(res);
  })
  .catch((err) => {
    console.log("err:", err);
  });
​

allSettled方法

  • 该方法会在所有的Promise都有结果(settled),无论是fulfilled,还是reject时,才会有最终的状态;
  • 并且这个Promise的结果一定是fulfilled的;
// allSettled
Promise.allSettled([p1, p2, p3])
  .then((res) => {
    console.log(res);
  })
  .catch((err) => {
    console.log(err);
  });
/*
[
  { status: 'fulfilled', value: 11111 },
  { status: 'rejected', reason: 22222 },
  { status: 'fulfilled', value: 33333 }
]
*/ 
  • 我们来看一下打印的结果:

    • allSettled的结果是一个数组,数组中存放着每一个Promise的结果,并且是对应一个对象的;
    • 这个对象中包含status状态,以及对应的value值;

race方法

  • 如果有一个Promise有了结果,我们就希望决定最终新Promise的状态,那么可以使用race方法:

    • race是竞技、竞赛的意思,表示多个Promise相互竞争,谁先有结果,那么就使用谁的结果;
// race: 竞技/竞赛
// 只要有一个Promise变成fulfilled状态, 那么就结束
// 意外:
Promise.race([p1, p2, p3])
  .then((res) => {
    console.log("res:", res);
  })
  .catch((err) => {
    console.log("err:", err);
  });

any方法

  • any方法是ES12中新增的方法,和race方法是类似的:

    • any方法会等到一个fulfilled状态,才会决定新Promise的状态;
    • 如果所有的Promise都是reject的,那么也会等到所有的Promise都变成rejected状态;
Promise.any([p1, p2, p3])
  .then((res) => {
    console.log("res:", res);
  })
  .catch((err) => {
    console.log("err:", err.errors);
  });
/*
[
    1111,
    22222,
    3333
]
*/
  • 如果所有的Promise都是reject的,那么会报一个AggregateError的错误

手写Promise

手写基础框架

// 设定promise的状态码
const PROMISE_STATUS_PENDING = "pending";
const PROMISE_STATUS_FULFILLED = "fulfilled";
const PROMISE_STATUS_REJECTED = "rejected";
​
class HYPromise {
  // 立即执行函数constructor初始化promise
  constructor(executor) {
    // Executor是在创建Promise时需要传入的一个回调函数,这个回调函数会被立即执行 通常我们会在Executor中确定我们的Promise状态
    this.status = PROMISE_STATUS_PENDING;
    this.value = undefined;
    this.reason = undefined;
​
    // resolve函数
    const resolve = (value) => {
      if (this.status === PROMISE_STATUS_PENDING) {
        // 更改状态
        this.status = PROMISE_STATUS_FULFILLED;
        this.value = value;
        console.log("resolve被调用");
      }
    };
​
    // reject函数
    const reject = (reason) => {
      if (this.status === PROMISE_STATUS_PENDING) {
        this.status = PROMISE_STATUS_REJECTED;
        this.reason = reason;
        console.log("reject被调用");
      }
    };
​
    // 返回executor
    executor(resolve, reject);
  }
}
​
const promise = new HYPromise((resolve, reject) => {
  console.log("状态pending");
  resolve(1111);
  reject(2222);
});

手写then方法(初始版)

  • then方法会有两个函数,一个成功回调,一个失败回调
  • 在调用then方法后会延时调用成功或者失败回调(因为此时在执行constructor初始化还没有调用then方法)
const PROMISE_STATUS_PENDING = "pending";
const PROMISE_STATUS_FULFILLED = "fulfilled";
const PROMISE_STATUS_REJECTED = "rejected";
​
class HYPromise {
  constructor(executor) {
    this.status = PROMISE_STATUS_PENDING;
    this.value = undefined;
    this.reason = undefined;
    const resolve = (value) => {
      if (this.status === PROMISE_STATUS_PENDING) {
        this.status = PROMISE_STATUS_FULFILLED;
        // queueMicrotask 延时调用 不然会this.onFulfilled is not a function
        queueMicrotask(() => {
          this.value = value;
          this.onFulfilled(this.value);
        });
      }
    };
    const reject = (reason) => {
      if (this.status === PROMISE_STATUS_PENDING) {
        this.status = PROMISE_STATUS_REJECTED;
        queueMicrotask(() => {
          console.log("执行onRejected");
          this.reason = reason;
          this.onRejected(this.reason);
        });
      }
    };
    executor(resolve, reject);
  }
​
  // then方法传入两个回调函数
  then(onFulfilled, onRejected) {
    console.log("执行then方法");
    this.onFulfilled = onFulfilled;
    this.onRejected = onRejected;
  }
}
​
const promise = new HYPromise((resolve, reject) => {
  console.log("状态pending");
  reject(2222)
  resolve(1111);
});
​
// 调用then方法
promise.then(
  (res) => {
    console.log("res1:", res);
    return 1111;
  },
  (err) => {
    console.log("err:", err);
  }
);
​
/*
状态pending
执行then方法
执行onRejected
err: 2222
*/

手写then方法(优化一)

  • 优化调用then方法不能多次调用
// ES6 ES2015
// https://promisesaplus.com/
const PROMISE_STATUS_PENDING = 'pending'
const PROMISE_STATUS_FULFILLED = 'fulfilled'
const PROMISE_STATUS_REJECTED = 'rejected'class HYPromise {
  constructor(executor) {
    this.status = PROMISE_STATUS_PENDING
    this.value = undefined
    this.reason = undefined
    // 新增两个Fns来存放多次调用的promise的回调函数
    this.onFulfilledFns = []
    this.onRejectedFns = []
​
    const resolve = (value) => {
      if (this.status === PROMISE_STATUS_PENDING) {
        // 添加微任务
        queueMicrotask(() => {
          // 再次检测promise状态
          if (this.status !== PROMISE_STATUS_PENDING) return
          this.status = PROMISE_STATUS_FULFILLED
          this.value = value
          // 轮流执行promise的回调
          this.onFulfilledFns.forEach(fn => {
            fn(this.value)
          })
        });
      }
    }
​
    const reject = (reason) => {
      if (this.status === PROMISE_STATUS_PENDING) {
        // 添加微任务
        queueMicrotask(() => {
          if (this.status !== PROMISE_STATUS_PENDING) return
          this.status = PROMISE_STATUS_REJECTED
          this.reason = reason
          this.onRejectedFns.forEach(fn => {
            fn(this.reason)
          })
        })
      }
    }
​
    executor(resolve, reject)
  }
​
  then(onFulfilled, onRejected) {
    // 1.如果在then调用的时候, 状态已经确定下来
    if (this.status === PROMISE_STATUS_FULFILLED && onFulfilled) {
      onFulfilled(this.value)
    }
    if (this.status === PROMISE_STATUS_REJECTED && onRejected) {
      onRejected(this.reason)
    }
​
    // 2.将成功回调和失败的回调放到数组中
    if (this.status === PROMISE_STATUS_PENDING) {
      this.onFulfilledFns.push(onFulfilled)
      this.onRejectedFns.push(onRejected)
    }
  }
}
​
const promise = new HYPromise((resolve, reject) => {
  console.log("状态pending")
  resolve(1111) // resolved/fulfilled
  reject(2222)
})
// 调用then方法多次调用
promise.then(res => {
  console.log("res1:", res)
}, err => {
  console.log("err:", err)
})
promise.then(res => {
  console.log("res2:", res)
}, err => {
  console.log("err2:", err)
})
// 在确定Promise状态之后, 再次调用then
setTimeout(() => {
  promise.then(res => {
    console.log("res3:", res)
  }, err => {
    console.log("err3:", err)
  })
}, 1000)
/*
状态pending
res1: 1111
res2: 1111
res3: 1111
*/ 

手写then方法(优化二)

  • 优化then方法不能链式调用
const PROMISE_STATUS_PENDING = "pending";
const PROMISE_STATUS_FULFILLED = "fulfilled";
const PROMISE_STATUS_REJECTED = "rejected";
​
// 工具函数 检测execFn能否正常执行,如果不行就抛出异常交给reject执行
function execFunctionWithCatchError(execFn, value, resolve, reject) {
  try {
    const result = execFn(value);
    resolve(result);
  } catch (err) {
    reject(err);
  }
}
​
class HYPromise {
  constructor(executor) {
    this.status = PROMISE_STATUS_PENDING;
    this.value = undefined;
    this.reason = undefined;
    this.onFulfilledFns = [];
    this.onRejectedFns = [];
​
    const resolve = (value) => {
      if (this.status === PROMISE_STATUS_PENDING) {
        // 添加微任务
        queueMicrotask(() => {
          if (this.status !== PROMISE_STATUS_PENDING) return;
          this.status = PROMISE_STATUS_FULFILLED;
          this.value = value;
          this.onFulfilledFns.forEach((fn) => {
            fn(this.value);
          });
        });
      }
    };
​
    const reject = (reason) => {
      if (this.status === PROMISE_STATUS_PENDING) {
        // 添加微任务
        queueMicrotask(() => {
          if (this.status !== PROMISE_STATUS_PENDING) return;
          this.status = PROMISE_STATUS_REJECTED;
          this.reason = reason;
          this.onRejectedFns.forEach((fn) => {
            fn(this.reason);
          });
        });
      }
    };
​
    try {
      executor(resolve, reject);
    } catch (err) {
      reject(err);
    }
  }
​
  then(onFulfilled, onRejected) {
    // 直接将一个新new出来的promise返回出去
    return new HYPromise((resolve, reject) => {
      // 1.如果在then调用的时候, 状态已经确定下来
      if (this.status === PROMISE_STATUS_FULFILLED && onFulfilled) {
        // try {
        //   const value = onFulfilled(this.value)
        //   resolve(value)
        // } catch(err) {
        //   reject(err)
        // }
        execFunctionWithCatchError(onFulfilled, this.value, resolve, reject);
      }
      if (this.status === PROMISE_STATUS_REJECTED && onRejected) {
        // try {
        //   const reason = onRejected(this.reason)
        //   resolve(reason)
        // } catch(err) {
        //   reject(err)
        // }
        execFunctionWithCatchError(onRejected, this.reason, resolve, reject);
      }
​
      // 2.将成功回调和失败的回调放到数组中
      if (this.status === PROMISE_STATUS_PENDING) {
        this.onFulfilledFns.push(() => {
          // try {
          //   const value = onFulfilled(this.value)
          //   resolve(value)
          // } catch(err) {
          //   reject(err)
          // }
          execFunctionWithCatchError(onFulfilled, this.value, resolve, reject);
        });
        this.onRejectedFns.push(() => {
          // try {
          //   const reason = onRejected(this.reason)
          //   resolve(reason)
          // } catch(err) {
          //   reject(err)
          // }
          execFunctionWithCatchError(onRejected, this.reason, resolve, reject);
        });
      }
    });
  }
}
​
const promise = new HYPromise((resolve, reject) => {
  console.log("状态pending");
  // resolve(1111) // resolved/fulfilled
  reject(2222);
  // throw new Error("executor error message")
});
​
// 调用then方法多次调用
promise
  .then(
    (res) => {
      console.log("res1:", res);
      return "aaaa";
      // 当抛出异常时会交给下一个promise的reject处理
      // throw new Error("err message")
    },
    (err) => {
      console.log("err1:", err);
      return "bbbbb";
      // throw new Error("err message")
    }
  )
  .then(
    (res) => {
      console.log("res2:", res);
    },
    (err) => {
      console.log("err2:", err);
    }
  );

手写catch方法

  • catch方法直接调用this.then(undefined, onRejected);
  • then方法检测一下onRejected是否为undefined,如果是undefined直接抛出异常交给下一个promise的reject处理
 // ES6 ES2015
// https://promisesaplus.com/
const PROMISE_STATUS_PENDING = "pending";
const PROMISE_STATUS_FULFILLED = "fulfilled";
const PROMISE_STATUS_REJECTED = "rejected";
​
// 工具函数
function execFunctionWithCatchError(execFn, value, resolve, reject) {
  try {
    const result = execFn(value);
    resolve(result);
  } catch (err) {
    reject(err);
  }
}
​
class HYPromise {
  constructor(executor) {
    this.status = PROMISE_STATUS_PENDING;
    this.value = undefined;
    this.reason = undefined;
    this.onFulfilledFns = [];
    this.onRejectedFns = [];
​
    const resolve = (value) => {
      if (this.status === PROMISE_STATUS_PENDING) {
        // 添加微任务
        queueMicrotask(() => {
          if (this.status !== PROMISE_STATUS_PENDING) return;
          this.status = PROMISE_STATUS_FULFILLED;
          this.value = value;
          this.onFulfilledFns.forEach((fn) => {
            fn(this.value);
          });
        });
      }
    };
​
    const reject = (reason) => {
      if (this.status === PROMISE_STATUS_PENDING) {
        // 添加微任务
        queueMicrotask(() => {
          if (this.status !== PROMISE_STATUS_PENDING) return;
          this.status = PROMISE_STATUS_REJECTED;
          this.reason = reason;
          this.onRejectedFns.forEach((fn) => {
            fn(this.reason);
          });
        });
      }
    };
​
    try {
      executor(resolve, reject);
    } catch (err) {
      reject(err);
    }
  }
​
  then(onFulfilled, onRejected) {
    // 当调用onRejected为underfined时,直接抛出异常交给下一个promise处理
    const defaultOnRejected = (err) => {
      throw err;
    };
    onRejected = onRejected || defaultOnRejected;
​
    return new HYPromise((resolve, reject) => {
      // 1.如果在then调用的时候, 状态已经确定下来
      if (this.status === PROMISE_STATUS_FULFILLED && onFulfilled) {
        execFunctionWithCatchError(onFulfilled, this.value, resolve, reject);
      }
      if (this.status === PROMISE_STATUS_REJECTED && onRejected) {
        execFunctionWithCatchError(onRejected, this.reason, resolve, reject);
      }
​
      // 2.将成功回调和失败的回调放到数组中
      if (this.status === PROMISE_STATUS_PENDING) {
        if (onFulfilled)
          this.onFulfilledFns.push(() => {
            execFunctionWithCatchError(
              onFulfilled,
              this.value,
              resolve,
              reject
            );
          });
        if (onRejected)
          this.onRejectedFns.push(() => {
            execFunctionWithCatchError(
              onRejected,
              this.reason,
              resolve,
              reject
            );
          });
      }
    });
  }
​
  catch(onRejected) {
    this.then(undefined, onRejected);
  }
}
​
const promise = new HYPromise((resolve, reject) => {
  console.log("状态pending");
  // resolve(1111) // resolved/fulfilled
  reject(2222);
});
​
// 调用then方法多次调用
promise
  .then((res) => {
    console.log("res:", res);
  })
  .catch((err) => {
    console.log("err:", err);
  });

手写finally方法

  • 再给onFulfilled也检测一下是否为underfined,如果为underfined则交给下一个promise的resolve处理
// ES6 ES2015
// https://promisesaplus.com/
const PROMISE_STATUS_PENDING = "pending";
const PROMISE_STATUS_FULFILLED = "fulfilled";
const PROMISE_STATUS_REJECTED = "rejected";
​
// 工具函数
function execFunctionWithCatchError(execFn, value, resolve, reject) {
  try {
    const result = execFn(value);
    resolve(result);
  } catch (err) {
    reject(err);
  }
}
​
class HYPromise {
  constructor(executor) {
    this.status = PROMISE_STATUS_PENDING;
    this.value = undefined;
    this.reason = undefined;
    this.onFulfilledFns = [];
    this.onRejectedFns = [];
​
    const resolve = (value) => {
      if (this.status === PROMISE_STATUS_PENDING) {
        // 添加微任务
        queueMicrotask(() => {
          if (this.status !== PROMISE_STATUS_PENDING) return;
          this.status = PROMISE_STATUS_FULFILLED;
          this.value = value;
          this.onFulfilledFns.forEach((fn) => {
            fn(this.value);
          });
        });
      }
    };
​
    const reject = (reason) => {
      if (this.status === PROMISE_STATUS_PENDING) {
        // 添加微任务
        queueMicrotask(() => {
          if (this.status !== PROMISE_STATUS_PENDING) return;
          this.status = PROMISE_STATUS_REJECTED;
          this.reason = reason;
          this.onRejectedFns.forEach((fn) => {
            fn(this.reason);
          });
        });
      }
    };
​
    try {
      executor(resolve, reject);
    } catch (err) {
      reject(err);
    }
  }
​
  then(onFulfilled, onRejected) {
    const defaultOnRejected = (err) => {
      throw err;
    };
    onRejected = onRejected || defaultOnRejected;
​
    // 在失败回调时将OnFulfilled交给下一个promise来处理
    const defaultOnFulfilled = (value) => {
      return value;
    };
    onFulfilled = onFulfilled || defaultOnFulfilled;
​
    return new HYPromise((resolve, reject) => {
      // 1.如果在then调用的时候, 状态已经确定下来 
      if (this.status === PROMISE_STATUS_FULFILLED && onFulfilled) {
        execFunctionWithCatchError(onFulfilled, this.value, resolve, reject);
      }
      if (this.status === PROMISE_STATUS_REJECTED && onRejected) {
        execFunctionWithCatchError(onRejected, this.reason, resolve, reject);
      }
​
      // 2.将成功回调和失败的回调放到数组中
      if (this.status === PROMISE_STATUS_PENDING) {
        if (onFulfilled)
          this.onFulfilledFns.push(() => {
            execFunctionWithCatchError(
              onFulfilled,
              this.value,
              resolve,
              reject
            );
          });
        if (onRejected)
          this.onRejectedFns.push(() => {
            execFunctionWithCatchError(
              onRejected,
              this.reason,
              resolve,
              reject
            );
          });
      }
    });
  }
​
  catch(onRejected) {
    return this.then(undefined, onRejected);
  }
​
  finally(onFinally) {
    this.then(
      () => {
        onFinally();
      },
      () => {
        onFinally();
      }
    );
  }
}
​
const promise = new HYPromise((resolve, reject) => {
  console.log("状态pending");
  resolve(1111); // resolved/fulfilled
  // reject(2222)
});
​
// 调用then方法多次调用
promise
  .then((res) => {
    console.log("res1:", res);
    return "aaaaa";
  })
  .then((res) => {
    console.log("res2:", res);
  })
  .catch((err) => {
    console.log("err:", err);
  })
  .finally(() => {
    console.log("finally");
  });

手写resolve和reject方法

  static resolve(value) {
    return new HYPromise((resolve) => resolve(value));
  }
​
  static reject(reason) {
    return new HYPromise((resolve, reject) => reject(reason));
  }
​
​
HYPromise.resolve("Hello World").then((res) => {
  console.log("res:", res);
});
​
HYPromise.reject("Error Message").catch((err) => {
  console.log("err:", err);
});
​

手写all和allSettled方法

static all(promises) {
    // 问题关键: 什么时候要执行resolve, 什么时候要执行reject
    return new HYPromise((resolve, reject) => {
      const values = [];
      promises.forEach((promise) => {
        promise.then(
          (res) => {
            values.push(res);
            if (values.length === promises.length) {
              resolve(values);
            }
          },
          (err) => {
            reject(err);
          }
        );
      });
    });
  }
​
  static allSettled(promises) {
    return new HYPromise((resolve) => {
      const results = [];
      promises.forEach((promise) => {
        promise.then(
          (res) => {
            results.push({ status: PROMISE_STATUS_FULFILLED, value: res });
            if (results.length === promises.length) {
              resolve(results);
            }
          },
          (err) => {
            results.push({ status: PROMISE_STATUS_REJECTED, value: err });
            if (results.length === promises.length) {
              resolve(results);
            }
          }
        ); 
      });
    });
  }
​
​
const p1 = new Promise((resolve) => {
  setTimeout(() => {
    resolve(1111);
  }, 1000);
});
const p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject(2222);
  }, 2000);
});
const p3 = new Promise((resolve) => {
  setTimeout(() => {
    resolve(3333);
  }, 3000);
});
HYPromise.all([p1, p2, p3]).then(res => {
  console.log(res)
}).catch(err => {
  console.log(err)
})
​
HYPromise.allSettled([p1, p2, p3]).then((res) => {
  console.log(res);
});

手写raceh和any方法

​
  static race(promises) {
    return new HYPromise((resolve, reject) => {
      promises.forEach((promise) => {
        // promise.then(res => {
        //   resolve(res)
        // }, err => {
        //   reject(err)
        // })
        promise.then(resolve, reject);
      });
    });
  }
​
  static any(promises) {
    // resolve必须等到有一个成功的结果
    // reject所有的都失败才执行reject
    const reasons = [];
    return new HYPromise((resolve, reject) => {
      promises.forEach((promise) => {
        promise.then(resolve, (err) => {
          reasons.push(err);
          if (reasons.length === promises.length) {
            reject(new AggregateError(reasons));
          }
        });
      });
    });
  }
​
​
const p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject(1111);
  }, 3000);
});
const p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject(2222);
  }, 2000);
});
const p3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject(3333);
  }, 3000);
});
​
HYPromise.race([p1, p2, p3]).then(res => {
  console.log("res:", res)
}).catch(err => {
  console.log("err:", err)
})
​
HYPromise.any([p1, p2, p3])
  .then((res) => {
    console.log("res:", res);
  })
  .catch((err) => {
    console.log("err:", err.errors);
  })

总结

一. Promise规范

promisesaplus.com/

二. Promise类设计

class HYPromise {}
function HYPromise() {}

三. 构造函数的规划

class HYPromise {
  constructor(executor) {
    // 定义状态
    // 定义resolve、reject回调
    // resolve执行微任务队列:改变状态、获取value、then传入执行成功回调
    // reject执行微任务队列:改变状态、获取reason、then传入执行失败回调
    
    // try catch
    executor(resolve, reject)
  }
}

四. then方法的实现

class HYPromise {
  then(onFulfilled, onRejected) {
    // this.onFulfilled = onFulfilled
    // this.onRejected = onRejected
    
    // 1.判断onFulfilled、onRejected,会给默认值
    
    // 2.返回Promise resolve/reject
    
    // 3.判断之前的promise状态是否确定
    // onFulfilled/onRejected直接执行(捕获异常)
    
    // 4.添加到数组中push(() => { 执行 onFulfilled/onRejected 直接执行代码})
  }
}

五. catch方法

class HYPromise {
  catch(onRejected) {
    return this.then(undefined, onRejected)
  }
}

六. finally

class HYPromise {
  finally(onFinally) {
    return this.then(() => {onFinally()}, () => {onFinally()})
  }
}

七. resolve/reject

八. all/allSettled

核心:要知道new Promise的resolve、reject在什么情况下执行

all:

  • 情况一:所有的都有结果
  • 情况二:有一个reject

allSettled:

  • 情况:所有都有结果,并且一定执行resolve

九.race/any

race:

  • 情况:只要有结果

any:

  • 情况一:必须等到一个resolve结果
  • 情况二:都没有resolve,所有的都是reject

实现代码

const PROMISE_STATUS_PENDING = "pending";
const PROMISE_STATUS_FULFILLED = "fulfilled";
const PROMISE_STATUS_REJECTED = "rejected";
​
function execFunctionWithCatchError(execFn, value, resolve, reject) {
  try {
    const result = execFn(value);
    resolve(result);
  } catch (err) {
    reject(err);
  }
}
​
class HYPromise {
  constructor(executor) {
    this.status = PROMISE_STATUS_PENDING;
    this.value = undefined;
    this.reason = undefined;
    this.onFulfilledFns = [];
    this.onRejectedFns = [];
​
    const resolve = (value) => {
      if (this.status === PROMISE_STATUS_PENDING) {
        queueMicrotask(() => {
          if (this.status !== PROMISE_STATUS_PENDING) return;
          this.status = PROMISE_STATUS_FULFILLED;
          this.value = value;
          this.onFulfilledFns.forEach((fn) => {
            fn(this.value);
          });
        });
      }
    };
​
    const reject = (reason) => {
      if (this.status === PROMISE_STATUS_PENDING) {
        queueMicrotask(() => {
          if (this.status !== PROMISE_STATUS_PENDING) return;
          this.status = PROMISE_STATUS_REJECTED;
          this.reason = reason;
          this.onRejectedFns.forEach((fn) => {
            fn(this.reason);
          });
        });
      }
    };
​
    try {
      executor(resolve, reject);
    } catch (err) {
      reject(err);
    }
  }
​
  then(onFulfilled, onRejected) {
    const defaultOnRejected = (err) => {
      throw err;
    };
    onRejected = onRejected || defaultOnRejected;
​
    const defaultOnFulfilled = (value) => {
      return value;
    };
    onFulfilled = onFulfilled || defaultOnFulfilled;
​
    return new HYPromise((resolve, reject) => {
      if (this.status === PROMISE_STATUS_FULFILLED && onFulfilled) {
        execFunctionWithCatchError(onFulfilled, this.value, resolve, reject);
      }
      if (this.status === PROMISE_STATUS_REJECTED && onRejected) {
        execFunctionWithCatchError(onRejected, this.reason, resolve, reject);
      }
      if (this.status === PROMISE_STATUS_PENDING) {
        if (onFulfilled)
          this.onFulfilledFns.push(() => {
            execFunctionWithCatchError(
              onFulfilled,
              this.value,
              resolve,
              reject
            );
          });
        if (onRejected)
          this.onRejectedFns.push(() => {
            execFunctionWithCatchError(
              onRejected,
              this.reason,
              resolve,
              reject
            );
          });
      }
    });
  }
​
  catch(onRejected) {
    return this.then(undefined, onRejected);
  }
​
  finally(onFinally) {
    this.then(
      () => {
        onFinally();
      },
      () => {
        onFinally();
      }
    );
  }
​
  static resolve(value) {
    return new HYPromise((resolve) => resolve(value));
  }
​
  static reject(reason) {
    return new HYPromise((resolve, reject) => reject(reason));
  }
​
  static all(promises) {
    return new HYPromise((resolve, reject) => {
      const values = [];
      promises.forEach((promise) => {
        promise.then(
          (res) => {
            values.push(res);
            if (values.length === promises.length) {
              resolve(values);
            }
          },
          (err) => {
            reject(err);
          }
        );
      });
    });
  }
​
  static allSettled(promises) {
    return new HYPromise((resolve) => {
      const results = [];
      promises.forEach((promise) => {
        promise.then(
          (res) => {
            results.push({ status: PROMISE_STATUS_FULFILLED, value: res });
            if (results.length === promises.length) {
              resolve(results);
            }
          },
          (err) => {
            results.push({ status: PROMISE_STATUS_REJECTED, value: err });
            if (results.length === promises.length) {
              resolve(results);
            }
          }
        );
      });
    });
  }
​
  static race(promises) {
    return new HYPromise((resolve, reject) => {
      promises.forEach((promise) => {
        // promise.then(res => {
        //   resolve(res)
        // }, err => {
        //   reject(err)
        // })
        promise.then(resolve, reject);
      });
    });
  }
​
  static any(promises) {
    // resolve必须等到有一个成功的结果
    // reject所有的都失败才执行reject
    const reasons = [];
    return new HYPromise((resolve, reject) => {
      promises.forEach((promise) => {
        promise.then(resolve, (err) => {
          reasons.push(err);
          if (reasons.length === promises.length) {
            reject(new AggregateError(reasons));
          }
        });
      });
    });
  }
}
​

十一、迭代器和生成器

迭代器

  • 迭代器(iterator),是确使用户可在容器对象(container,例如链表或数组)上遍访的对象,使用该接口无需关心对象的内部实现细节。

    • 其行为像数据库中的光标,迭代器最早出现在1974年设计的CLU编程语言中;
    • 在各种编程语言的实现中,迭代器的实现方式各不相同,但是基本都有迭代器,比如Java、Python等;
  • 从迭代器的定义我们可以看出来,迭代器是帮助我们对某个数据结构进行遍历的对象。

  • 在JavaScript中,迭代器也是一个具体的对象,这个对象需要符合迭代器协议(iterator protocol):

    • 迭代器协议定义了产生一系列值(无论是有限还是无限个)的标准方式;
    • 那么在js中这个标准就是一个特定的next方法;
  • next方法有如下的要求:

    • 一个无参数或者一个参数的函数,返回一个应当拥有以下两个属性的对象:

    • done(boolean)

      • 如果迭代器可以产生序列中的下一个值,则为 false。(这等价于没有指定 done 这个属性。)
      • 如果迭代器已将序列迭代完毕,则为 true。这种情况下,value 是可选的,如果它依然存在,即为迭代结束之后的默认返回值。
    • value

      • 迭代器返回的任何 JavaScript 值。done 为 true 时可省略
// 编写的一个迭代器
const iterator = {
  next: function() {
    return { done: true, value: 123 }
  }
}
​
// 数组
const names = ["abc", "cba", "nba"]
​
// 创建一个迭代器对象来访问数组
let index = 0const namesIterator = {
  next: function() {
    if (index < names.length) {
      return { done: false, value: names[index++] }
    } else {
      return { done: true, value: undefined }
    }
  }
}
​
console.log(namesIterator.next()) // { done: false, value: "nba" }
console.log(namesIterator.next()) // { done: true, value: undefined }

可迭代对象

  • 但是上面的代码整体来说看起来是有点奇怪的:

    • 我们获取一个数组的时候,需要自己创建一个index变量,再创建一个所谓的迭代器对象;
    • 事实上我们可以对上面的代码进行进一步的封装,让其变成一个可迭代对象;
  • 什么又是可迭代对象呢?

    • 它和迭代器是不同的概念;
    • 当一个对象实现了iterable protocol协议时,它就是一个可迭代对象;
    • 这个对象的要求是必须实现 @@iterator 方法,在代码中我们使用 Symbol.iterator 访问该属性;
  • 当我们要问一个问题,我们转成这样的一个东西有什么好处呢?

    • 当一个对象变成一个可迭代对象的时候,进行某些迭代操作,比如 for...of 操作时,其实就会调用它的@@iterator 方法;

image-20220914125600655

// 创建一个迭代器对象来访问数组
const iterableObj = {
  names: ["abc", "cba", "nba"],
  [Symbol.iterator]: function() {
    let index = 0
    return {
      next: () => {
        if (index < this.names.length) {
          return { done: false, value: this.names[index++] }
        } else {
          return { done: true, value: undefined }
        }
      }
    }
  }
}

原生迭代器对象

事实上我们平时创建的很多原生对象已经实现了可迭代协议,会生成一个迭代器对象的:

  • String、Array、Map、Set、arguments对象、NodeList集合;
const names = ["abc", "cba", "nba"]
console.log(names[Symbol.iterator])
​
const iterator1 = names[Symbol.iterator]()
console.log(iterator1.next())
console.log(iterator1.next())
​
for (const item of names) {
  console.log(item)
}
​
// Map/Set
const set = new Set()
set.add(10)
set.add(100)
set.add(1000)
​
console.log(set[Symbol.iterator])
​
for (const item of set) {
  console.log(item)
}
​
// 函数中arguments也是一个可迭代对象
function foo(x, y, z) {
  console.log(arguments[Symbol.iterator])
  for (const arg of arguments) {
    console.log(arg)
  }
}
​
foo(10, 20, 30)

可迭代对象的应用

那么这些东西可以被用在哪里呢?

  • JavaScript中语法:for ...of、展开语法(spread syntax)、yield*(后面讲)、解构赋值(Destructuring_assignment);
  • 创建一些对象时:new Map([Iterable])、new WeakMap([iterable])、new Set([iterable])、new WeakSet([iterable]);
  • 一些方法的调用:Promise.all(iterable)、Promise.race(iterable)、Array.from(iterable);
// 1.for of场景// 2.展开语法(spread syntax)
const iterableObj = {
  names: ["abc", "cba", "nba"],
  [Symbol.iterator]: function () {
    let index = 0
    return {
      next: () => {
        if (index < this.names.length) {
          return { done: false, value: this.names[index++] }
        } else {
          return { done: true, value: undefined }
        }
      }
    }
  }
}
​
const names = ["abc", "cba", "nba"]
const newNames = [...names, ...iterableObj]
console.log(newNames)
​
const obj = { name: "why", age: 18 }
// for (const item of obj) {// }
// ES9(ES2018)中新增的一个特性: 用的不是迭代器
const newObj = { ...obj }
console.log(newObj)
​
​
// 3.解构语法
const [name1, name2] = names
// const { name, age } = obj 不一样ES9新增的特性// 4.创建一些其他对象时
const set1 = new Set(iterableObj)
const set2 = new Set(names)
​
const arr1 = Array.from(iterableObj)
​
// 5.Promise.all
Promise.all(iterableObj).then(res => {
  console.log(res)
})

自定义类的迭代

在前面我们看到Array、Set、String、Map等类创建出来的对象都是可迭代对象:

  • 在面向对象开发中,我们可以通过class定义一个自己的类,这个类可以创建很多的对象:
  • 如果我们也希望自己的类创建出来的对象默认是可迭代的,那么在设计类的时候我们就可以添加上@@iterator 方法;
// 案例: 创建一个教室类, 创建出来的对象都是可迭代对象
class Classroom {
  constructor(address, name, students) {
    this.address = address
    this.name = name
    this.students = students
  }
​
  entry(newStudent) {
    this.students.push(newStudent)
  }
​
  [Symbol.iterator]() {
    let index = 0
    return {
      next: () => {
        if (index < this.students.length) {
          return { done: false, value: this.students[index++] }
        } else {
          return { done: true, value: undefined }
        }
      },
      return: () => {
        console.log("迭代器提前终止了~")
        return { done: true, value: undefined }
      }
    }
  }
}
​
const classroom = new Classroom("3幢5楼205", "计算机教室", ["james", "kobe", "curry", "why"])
classroom.entry("lilei")
​
for (const stu of classroom) {
  console.log(stu)
  if (stu === "why") break
}
/*
james
kobe
curry
why
迭代器提前终止了~
*/

迭代器的中断

  • 迭代器在某些情况下会在没有完全迭代的情况下中断:

    • 比如遍历的过程中通过break、continue、return、throw中断了循环操作;
    • 比如在解构的时候,没有解构所有的值;
  • 那么这个时候我们想要监听中断的话,可以添加return方法

[Symbol.iterator]() {
    let index = 0
    return {
      next: () => {
        if (index < this.students.length) {
          return { done: false, value: this.students[index++] }
        } else {
          return { done: true, value: undefined }
        }
      },
      return: () => {
        console.log("迭代器提前终止了~")
        return { done: true, value: undefined }
      }
    }
}

生成器

  • 生成器是ES6中新增的一种函数控制、使用的方案,它可以让我们更加灵活的控制函数什么时候继续执行、暂停执行等。

  • 平时我们会编写很多的函数,这些函数终止的条件通常是返回值或者发生了异常。

  • 生成器函数也是一个函数,但是和普通的函数有一些区别:

    • 首先,生成器函数需要在function的后面加一个符号:*

    • 其次,生成器函数可以通过yield关键字来控制函数的执行流程:

    • 最后,生成器函数的返回值是一个Generator(生成器):

      • 生成器事实上是一种特殊的迭代器;

生成器函数执行

我们发现上面的生成器函数foo的执行体压根没有执行,它只是返回了一个生成器对象。

  • 那么我们如何可以让它执行函数中的东西呢?调用next即可;
  • 我们之前学习迭代器时,知道迭代器的next是会有返回值的;
  • 但是我们很多时候不希望next返回的是一个undefined,这个时候我们可以通过yield来返回结果;
// 当遇到yield时候值暂停函数的执行
// 当遇到return时候生成器就停止执行
function* foo() {
  console.log("函数开始执行~")
​
  const value1 = 100
  console.log("第一段代码:", value1)
  yield value1
​
  const value2 = 200
  console.log("第二段代码:", value2)
  yield value2
​
  const value3 = 300
  console.log("第三段代码:", value3)
  yield value3
​
  console.log("函数执行结束~")
  return "123"
}
​
// 调用生成器函数时, 会给我们返回一个生成器对象
// generator本质上是一个特殊的iterator 
const generator = foo()
// 开始执行第一段代码
console.log("返回值1:", generator.next())
console.log("返回值2:", generator.next())
console.log("返回值3:", generator.next())
console.log("返回值3:", generator.next())
​
/*
函数开始执行~
第一段代码: 100
返回值1: { value: 100, done: false }
第二段代码: 200
返回值2: { value: 200, done: false }
第三段代码: 300
返回值3: { value: 300, done: false }
函数执行结束~
返回值3: { value: '123', done: true }
*/

生成器传递参数 – next函数

函数既然可以暂停来分段执行,那么函数应该是可以传递参数的,我们是否可以给每个分段来传递参数呢?

  • 答案是可以的;
  • 我们在调用next函数的时候,可以给它传递参数,那么这个参数会作为上一个yield语句的返回值;
  • 注意:也就是说我们是为本次的函数代码块执行提供了一个值;
function* foo(num) {
  console.log("函数开始执行~")
​
  const value1 = 100 * num
  console.log("第一段代码:", value1)
  const n = yield value1
​
  const value2 = 200 * n
  console.log("第二段代码:", value2)
  const count = yield value2
​
  const value3 = 300 * count
  console.log("第三段代码:", value3)
  yield value3
​
  console.log("函数执行结束~")
  return "123"
}
​
// 生成器上的next方法可以传递参数
const generator = foo(5)
console.log(generator.next())
// 第二段代码, 第二次调用next的时候执行的
console.log(generator.next(10))
console.log(generator.next(25))
​
/*
函数开始执行~
第一段代码: 500
{ value: 500, done: false }
第二段代码: 2000
{ value: 2000, done: false }
第三段代码: 7500
{ value: 7500, done: false }
*/

生成器提前结束 – return函数

还有一个可以给生成器函数传递参数的方法是通过return函数:

  • return传值后这个生成器函数就会结束,之后调用next不会继续生成值了;
function* foo(num) {
  console.log("函数开始执行~")
​
  const value1 = 100 * num
  console.log("第一段代码:", value1)
  const n = yield value1
​
  const value2 = 200 * n
  console.log("第二段代码:", value2)
  const count = yield value2
​
  const value3 = 300 * count
  console.log("第三段代码:", value3)
  yield value3
​
  console.log("函数执行结束~")
  return "123"
}
​
const generator = foo(10)
​
console.log(generator.next())
​
// 第二段代码的执行, 使用了return
// 那么就意味着相当于在第一段代码的后面加上return, 就会提前终端生成器函数代码继续执行
console.log(generator.return(15))
console.log(generator.next())
console.log(generator.next())
/*
函数开始执行~
第一段代码: 1000
{ value: 1000, done: false }
{ value: 15, done: true }
{ value: undefined, done: true }
{ value: undefined, done: true }
*/

生成器抛出异常 – throw函数

除了给生成器函数内部传递参数之外,也可以给生成器函数内部抛出异常:

  • 抛出异常后我们可以在生成器函数中捕获异常;
  • 但是在catch语句中不能继续yield新的值了,但是可以在catch语句外使用yield继续中断函数的执行;
function* foo() {
  console.log("代码开始执行~")
  const value1 = 100
  try {
    yield value1
  } catch (error) {
    console.log("捕获到异常情况:", error)
    yield "abc"
  }
  console.log("第二段代码继续执行")
  const value2 = 200
  yield value2
  console.log("代码执行结束~")
}
​
const generator = foo()
​
const result = generator.next()
generator.throw("error message")
​
/*
代码开始执行~
捕获到异常情况: error message
*/ 

生成器替代迭代器

  • 我们发现生成器是一种特殊的迭代器,那么在某些情况下我们可以使用生成器来替代迭代器:

  • 事实上我们还可以使用yield*来生产一个可迭代对象:

    • 这个时候相当于是一种yield的语法糖,只不过会依次迭代这个可迭代对象,每次迭代其中的一个值;
// 1.生成器来替代迭代器
function* createArrayIterator(arr) {
​
  // 3.第三种写法 yield*
  yield* arr
​
  // 2.第二种写法
  for (const item of arr) {
    yield item
  }
  // 1.第一种写法
  yield "abc" // { done: false, value: "abc" }
  yield "cba" // { done: false, value: "abc" }
  yield "nba" // { done: false, value: "abc" }
}

创建一个函数, 这个函数可以迭代一个范围内的数字:

// 2.创建一个函数, 这个函数可以迭代一个范围内的数字
// 10 20
function* createRangeIterator(start, end) {
  let index = start
  while (index < end) {
    yield index++
  }

在之前的自定义类迭代中,我们也可以换成生成器:

// 3.class案例
class Classroom {
  constructor(address, name, students) {
    this.address = address
    this.name = name
    this.students = students
  }
  entry(newStudent) {
    this.students.push(newStudent)
  }
  foo = () => {
    console.log("foo function")
  }
  // [Symbol.iterator] = function*() {
  //   yield* this.students
  // }
  *[Symbol.iterator]() {
    yield* this.students
  }
}
​
const classroom = new Classroom("3幢", "1102", ["abc", "cba"])
for (const item of classroom) {
  console.log(item)
}
/*
abc
cba
*/

异步处理方案

  • 学完了我们前面的Promise、生成器等,我们目前来看一下异步代码的最终处理方案。

  • 需求:

    • 我们需要向服务器发送网络请求获取数据,一共需要发送三次请求;
    • 第二次的请求url依赖于第一次的结果;
    • 第三次的请求url依赖于第二次的结果;
    • 依次类推;
// request.js
function requestData(url) {
  // 异步请求的代码会被放入到executor中
  return new Promise((resolve, reject) => {
    // 模拟网络请求
    setTimeout(() => {
      // 拿到请求的结果
      resolve(url)
    }, 2000);
  })
}
​
// 需求: 
// 1> url: why -> res: why
// 2> url: res + "aaa" -> res: whyaaa
// 3> url: res + "bbb" => res: whyaaabbb// 1.第一种方案: 多次回调
// 回调地狱
requestData("why").then(res => {
  requestData(res + "aaa").then(res => {
    requestData(res + "bbb").then(res => {
      console.log(res)
    })
  })
})
​
​
// 2.第二种方案: Promise中then的返回值来解决
requestData("why").then(res => {
  return requestData(res + "aaa")
}).then(res => {
  return requestData(res + "bbb")
}).then(res => {
  console.log(res)
})
​
// 3.第三种方案: Promise + generator实现
function* getData() {
  const res1 = yield requestData("why")
  const res2 = yield requestData(res1 + "aaa")
  const res3 = yield requestData(res2 + "bbb")
  const res4 = yield requestData(res3 + "ccc")
  console.log(res4)
}

自动执行generator函数

  • 目前我们的写法有两个问题:

    • 第一,我们不能确定到底需要调用几层的Promise关系;
    • 第二,如果还有其他需要这样执行的函数,我们应该如何操作呢?
  • 所以,我们可以封装一个工具函数execGenerator自动执行生成器函数:

function* getData() {
  const res1 = yield requestData("why")
  const res2 = yield requestData(res1 + "aaa")
  const res3 = yield requestData(res2 + "bbb")
  const res4 = yield requestData(res3 + "ccc")
  console.log(res4)
}
​
// 1> 手动执行生成器函数
const generator = getData()
generator.next().value.then(res => {
  generator.next(res).value.then(res => {
    generator.next(res).value.then(res => {
      generator.next(res)
    })
  })
})
​
// 2> 自己封装了一个自动执行的函数
function execGenerator(genFn) {
  const generator = genFn()
  function exec(res) {
    const result = generator.next(res)
    if (result.done) {
      return result.value
    }
    result.value.then(res => {
      exec(res)
    })
  }
  exec()
}
​
execGenerator(getData)
​
// 3> 第三方包co自动执行
const co = require('co')
co(getData)
​
​
// 4.第四种方案: async/await
async function getData() {
  const res1 = await requestData("why")
  const res2 = await requestData(res1 + "aaa")
  const res3 = await requestData(res2 + "bbb")
  const res4 = await requestData(res3 + "ccc")
  console.log(res4)
}
​
getData()

异步函数 async function

  • async关键字用于声明一个异步函数:

    • async是asynchronous单词的缩写,异步、非同步;
    • sync是synchronous单词的缩写,同步、同时;
  • async异步函数可以有很多中写法:

// await/async
async function foo1() {
​
}
​
const foo2 = async () => {
​
}
​
class Foo {
  async bar() {
​
  }
}

异步函数的执行流程

  • 异步函数的内部代码执行过程和普通的函数是一致的,默认情况下也是会被同步执行。

  • 异步函数有返回值时,和普通函数会有区别:

    • 情况一:异步函数也可以有返回值,但是异步函数的返回值会被包裹到Promise.resolve中;
    • 情况二:如果我们的异步函数的返回值是Promise,Promise.resolve的状态会由Promise决定;
    • 情况三:如果我们的异步函数的返回值是一个对象并且实现了thenable,那么会由对象的then方法来决定;
async function foo() {
  console.log("foo function start~")
  console.log("中间代码~")
  console.log("foo function end~")
​
  // 1.返回一个值
  // return aaa
​
  // 2.返回thenable
  return {
    then: function(resolve, reject) {
      resolve("hahahah")
    }
  }
​
  // 3.返回Promise
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve("hehehehe")
    }, 2000)
  })
}
​
// 异步函数的返回值一定是一个Promise
const promise = foo()
promise.then(res => {
  console.log("promise then function exec:", res)
})
​
/*
foo function start~
中间代码~
foo function end~
promise then function exec: hehehehe
*/
  • 如果我们在async中抛出了异常,那么程序它并不会像普通函数一样报错,而是会作为Promise的reject来传递;
async function foo() {
  console.log("foo function start~")
  console.log("中间代码~")
  // 异步函数中的异常, 会被作为异步函数返回的Promise的reject值的
  throw new Error("error message")
​
  console.log("foo function end~")
}
​
// 异步函数的返回值一定是一个Promise
foo().catch(err => {
  console.log("coderwhy err:", err)
})
​
console.log("后续还有代码~~~~~")
​

await关键字

  • async函数另外一个特殊之处就是可以在它内部使用await关键字,而普通函数中是不可以的。

  • await关键字有什么特点呢?

    • 通常使用await是后面会跟上一个表达式,这个表达式会返回一个Promise;
    • 那么await会等到Promise的状态变成fulfilled状态,之后继续执行异步函数;
  • 如果await后面是一个普通的值,那么会直接返回这个值;

  • 如果await后面是一个thenable的对象,那么会根据对象的then方法调用来决定后续的值;

  • 如果await后面的表达式,返回的Promise是reject的状态,那么会将这个reject结果直接作为函数的Promise的reject值;

// 1.await更上表达式
function requestData() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      // resolve(222)
      reject(1111)
    }, 2000);
  })
}
​
// async function foo() {
//   const res1 = await requestData()
//   console.log("后面的代码1", res1)
//   console.log("后面的代码2")
//   console.log("后面的代码3")//   const res2 = await requestData()
//   console.log("res2后面的代码", res2)
// }// 2.跟上其他的值
async function foo() {
  // const res1 = await 123
  // const res1 = await {
  //   then: function(resolve, reject) {
  //     resolve("abc")
  //   }
  // }
  const res1 = await new Promise((resolve) => {
    resolve("why")
  })
  console.log("res1:", res1)
}
​
// 3.reject值
async function foo() {
  const res1 = await requestData()
  console.log("res1:", res1)
}
​
foo().catch(err => {
  console.log("err:", err)
})

十二、事件循环

进程和线程

  • 线程和进程是操作系统中的两个概念:

    • 进程(process):计算机已经运行的程序,是操作系统管理程序的一种方式;
    • 线程(thread):操作系统能够运行运算调度的最小单位,通常情况下它被包含在进程中;
  • 听起来很抽象,这里还是给出我的解释:

    • 进程:我们可以认为,启动一个应用程序,就会默认启动一个进程(也可能是多个进程);
    • 线程:每一个进程中,都会启动至少一个线程用来执行程序中的代码,这个线程被称之为主线程;
    • 所以我们也可以说进程是线程的容器;
  • 再用一个形象的例子解释:

    • 操作系统类似于一个大工厂;
    • 工厂中里有很多车间,这个车间就是进程;
    • 每个车间可能有一个以上的工人在工厂,这个工人就是线程;

image-20220919001224723

  • 操作系统是如何做到同时让多个进程(边听歌、边写代码、边查阅资料)同时工作呢?

    • 这是因为CPU的运算速度非常快,它可以快速的在多个进程之间迅速的切换;
    • 当我们进程中的线程获取到时间片时,就可以快速执行我们编写的代码;
    • 对于用户来说是感受不到这种快速的切换的

浏览器中的JavaScript线程

  • 我们经常会说JavaScript是单线程的,但是JavaScript的线程应该有自己的容器进程:浏览器或者Node。

  • 浏览器是一个进程吗,它里面只有一个线程吗?

    • 目前多数的浏览器其实都是多进程的,当我们打开一个tab页面时就会开启一个新的进程,这是为了防止一个页面卡死而造成所有页面无法响应,整个浏览器需要强制退出;
    • 每个进程中又有很多的线程,其中包括执行JavaScript代码的线程;
  • JavaScript的代码执行是在一个单独的线程中执行的:

    • 这就意味着JavaScript的代码,在同一个时刻只能做一件事;
    • 如果这件事是非常耗时的,就意味着当前的线程就会被阻塞;
  • 所以真正耗时的操作,实际上并不是由JavaScript线程在执行的:

    • 浏览器的每个进程是多线程的,那么其他线程可以来完成这个耗时的操作;
    • 比如网络请求、定时器,我们只需要在特性的时候执行应该有的回调即可;

浏览器的事件循环

  • 如果在执行JavaScript代码的过程中,有异步操作呢?

    • 中间我们插入了一个setTimeout的函数调用;
    • 这个函数被放到入调用栈中,执行会立即结束,并不会阻塞后续代码的执行;

image-20220919001443262

宏任务和微任务

  • 但是事件循环中并非只维护着一个队列,事实上是有两个队列:

    • 宏任务队列(macrotask queue):ajax、setTimeout、setInterval、DOM监听、UI Rendering等
    • 微任务队列(microtask queue):Promise的then回调、 Mutation Observer API、queueMicrotask()等
  • 那么事件循环对于两个队列的优先级是怎么样的呢?

    1. main script中的代码优先执行(编写的顶层script代码);

    2. 在执行任何一个宏任务之前(不是队列,是一个宏任务),都会先查看微任务队列中是否有任务需要执行

      • 也就是宏任务执行之前,必须保证微任务队列是空的;
      • 如果不为空,那么就优先执行微任务队列中的任务(回调);
setTimeout(() => {
  console.log("setTimeout")
}, 1000)
​
queueMicrotask(() => {
  console.log("queueMicrotask")
})
​
Promise.resolve().then(() => {
  console.log("Promise then")
})
​
function foo() {
  console.log("foo")
}
​
function bar() {
  console.log("bar")
  foo()
}
​
bar()
​
console.log("其他代码")
// bar
// foo
// 其他代码
// queueMicrotask
// Promise then
// setTimeout

Node的事件循环

  • 浏览器中的EventLoop是根据HTML5定义的规范来实现的,不同的浏览器可能会有不同的实现,而Node中是由libuv实现的。

  • 这里我们来给出一个Node的架构图:

    • 我们会发现libuv中主要维护了一个EventLoop和worker threads(线程池);
    • EventLoop负责调用系统的一些其他操作:文件的IO、Network、child-processes等
  • libuv是一个多平台的专注于异步IO的库,它最初是为Node开发的,但是现在也被使用到Luvit、Julia、pyuv等其他地方;

image-20220919001913199

Node事件循环的阶段

  • 我们最前面就强调过,事件循环像是一个桥梁,是连接着应用程序的JavaScript和系统调用之间的通道:

    • 无论是我们的文件IO、数据库、网络IO、定时器、子进程,在完成对应的操作后,都会将对应的结果和回调函数放到事件循环(任务队列)中;
    • 事件循环会不断的从任务队列中取出对应的事件(回调函数)来执行;
  • 但是一次完整的事件循环Tick分成很多个阶段:

    • 定时器(Timers) :本阶段执行已经被 setTimeout() 和 setInterval() 的调度回调函数。
    • 待定回调(Pending Callback) :对某些系统操作(如TCP错误类型)执行回调,比如TCP连接时接收到 ECONNREFUSED。
    • idle, prepare :仅系统内部使用。
    • 轮询(Poll) :检索新的 I/O 事件;执行与 I/O 相关的回调;
    • 检测(check) :setImmediate() 回调函数在这里执行。
    • 关闭的回调函数**:一些关闭的回调函数,如:socket.on('close', ...)。

image-20220919002025824

Node的宏任务和微任务

  • 我们会发现从一次事件循环的Tick来说,Node的事件循环更复杂,它也分为微任务和宏任务:

    • 宏任务(macrotask):setTimeout、setInterval、IO事件、setImmediate、close事件;
    • 微任务(microtask):Promise的then回调、process.nextTick、queueMicrotask;
  • 但是,Node中的事件循环不只是 微任务队列和 宏任务队列:

    • 微任务队列:

      • next tick queue:process.nextTick;
      • other queue:Promise的then回调、queueMicrotask;
    • 宏任务队列:

      • timer queue:setTimeout、setInterval;
      • poll queue:IO事件
      • check queue:setImmediate;
      • close queue:close事件;
  • 所以,在每一次事件循环的tick中,会按照如下顺序来执行代码:

    • next tick microtask queue;
    • other microtask queue;
    • timer queue;
    • poll queue;
    • check queue;
    • close queue;

Promise面试题

第一题

setTimeout(function () {
  console.log("setTimeout1");
  new Promise(function (resolve) {
    resolve();
  }).then(function () {
    new Promise(function (resolve) {
      resolve();
    }).then(function () {
      console.log("then4");
    });
    console.log("then2");
  });
});
​
new Promise(function (resolve) {
  console.log("promise1");
  resolve();
}).then(function () {
  console.log("then1");
});
​
setTimeout(function () {
  console.log("setTimeout2");
});
​
console.log(2);
​
queueMicrotask(() => {
  console.log("queueMicrotask1")
});
​
new Promise(function (resolve) {
  resolve();
}).then(function () {
  console.log("then3");
});
​
// promise1
// 2
// then1
// queueMicrotask1
// then3
// setTimeout1
// then2
// then4
// setTimeout2

image-20220918181055458

第二题

async function async1() {
  console.log('async1 start')
  await async2();
  console.log('async1 end')
}
​
async function async2() {
  console.log('async2')
}
​
console.log('script start')
​
setTimeout(function () {
  console.log('setTimeout')
}, 0)
​
async1();
​
new Promise(function (resolve) {
  console.log('promise1')
  resolve();
}).then(function () {
  console.log('promise2')
})
​
console.log('script end')
// script start
// async1 start
// async2
// promise1
// script end
// async1 end
// promise2
// setTimeout

image-20220918181922854

第三题

Promise.resolve().then(() => {
  console.log(0);
  // 1.•••直接return一个值 相当于resolve(4)
  // return 4
​
  // 2.return thenable的值
  return {
    then: function(resolve) {
      // 大量的计算
      resolve(4)
    }
  }
​
  // 3.return Promise
  // 不是普通的值, 多加一次微任务
  // Promise.resolve(4), 多加一次微任务
  // 一共多加两次微任务
  return Promise.resolve(4)
}).then((res) => {
  console.log(res)
})
​
Promise.resolve().then(() => {
  console.log(1);
}).then(() => {
  console.log(2);
}).then(() => {
  console.log(3);
}).then(() => {
  console.log(5);
}).then(() =>{
  console.log(6);
})
​
// 1.return 4
// 0
// 1
// 4
// 2
// 3
// 5
// 6// 2.return thenable
// 0
// 1
// 2
// 4
// 3
// 5
// 6// 3.return promise
// 0
// 1
// 2
// 3
// 4
// 5
// 6

image-20220918201744568

image-20220918204323169

第四题(node环境)

async function async1() {
  console.log('async1 start')
  await async2()
  console.log('async1 end')
}
​
async function async2() {
  console.log('async2')
}
​
console.log('script start')
​
setTimeout(function () {
  console.log('setTimeout0')
}, 0)
​
setTimeout(function () {
  console.log('setTimeout2')
}, 300)
​
setImmediate(() => console.log('setImmediate'));
​
process.nextTick(() => console.log('nextTick1'));
​
async1();
​
process.nextTick(() => console.log('nextTick2'));
​
new Promise(function (resolve) {
  console.log('promise1')
  resolve();
  console.log('promise2')
}).then(function () {
  console.log('promise3')
})
​
console.log('script end')
​
// script start
// async1 start
// async2
// promise1
// promise2
// script end
// nexttick1
// nexttick2
// async1 end
// promise3
// settimetout0
// setImmediate
// setTimeout2