区分并发和并行
并发和并行区别是什么?
- 并发是宏观概念,假设分别有任务A和任务B,在一段时间内通过任务间的切换完成了这两个任务,这种情况可以称为并发。
- 并行是微观概念,假设cpu存在两个核心,那么就可以同时完成任务A、B,同时完成多个任务的情况就可以称为并行。
普通的回调函数有什么缺点?
致命的弱点,就是容易写出回调地狱(Callback hell),让代码看起来不利于阅读和维护,根本问题:
- 嵌套函数存在耦合性,一旦有所改动,就会牵一发而动全身
- 嵌套函数一多,就很难处理错误
- 不能使用 try catch 捕获错误,不能直接 return。
Promise
Promise表示一个异步操作的最终结果,与之进行交互的方式主要是then方法,该方法注册了两个回调函数,用于接收promise的终值或者promise不能执行的原因。
Promise是承诺的意思,这个承诺有三种状态:
- 等待态 pending
- 完成态 resolved
- 拒绝态 rejected
这个承诺一旦从等待状态变成其他状态就不能更改了,例如:
1 | new Promise((resolve, reject) => { |
在构造Promise的时候,构造函数内部的代码是立即执行的
1 | new Promise((resolve, reject) => { |
- Promise 实现了链式调用,也就是说每次调用 then 之后返回的都是一个 Promise,并且是一个全新的 Promise,原因也是因为状态不可变。
- 如果你在 then 中 使用了 return,那么 return 的值会被 Promise.resolve() 包装。
1 | Promise.resolve(1) |
如何实现一个简易版promise?
- 创建三个常量表示promise的状态
- 在函数体内部首先保存this,因为代码可能会异步执行,用于获取正确的 this对象
- 开始promise的状态应该是pending
- value 变量用于保存 resolve 或者 reject 中传入的值
- resolvedCallbacks 和 rejectedCallbacks 用于保存 then 中的回调,因为当执行完 Promise 时状态可能还是等待中,这时候应该把 then 中的回调保存起来用于状态改变时使用
1 | const PENDING = 'pending' // 等待态 |
关于resolve、reject两个函数的说明:
- 首先两个函数都要判断当前状态是否为等待中,只有等待中才可以改变状态
- 将当前状态更改为对应状态,并且将传入的值赋给value
- 遍历回调数组并执行
关于执行Promise中传入的函数的说明:
- 执行传入的参数并且将之前两个函数当做参数传进去
- 当执行异步操作时有可能发生异常,需要try/catch捕获到异常,并使promise进入rejected状态
最后是then函数的实现:
- 熟悉判断两个参数是否为函数类型,因为这两个参数是可选参数
- 当参数不是函数类型,需要创建一个函数赋值给对应的参数,同时也实现了透传,例如:
1 | // 该代码目前在简单版中会报错 |
- 然后就是一系列判断状态的逻辑,当状态不是等待态时,就去执行相对应的函数。如果状态是等待态的话,就往回调函数中 push 函数
1 | Promise1.prototype.then = function(onFulfilled, onRejected) { |
测试用例1:
1 | let promise = new Promise1((resolve, reject) => { |
测试用例2:
1 | let promise = new Promise1(function(resolve,reject){ |
⚠️ 简易版promise总结:
1 | const PENDING = 'pending' // 等待态 |
Promise A+ 规范
核心的 Promises/A+ 规范不设计如何创建、解决和拒绝 promise,而是专注于提供一个通用的 then 方法。
根据规范完善Promise
1.链式回调的异常处理
then中无论是执行成功的回调还是失败回调,只要有返回结果,都会走到下一个then(根据不同返回结果进入下一个then的不同回调)
promise是通过then中返回新的promise来实现链式调用的,一个新的promise是可以继续调用then方法的,补充then方法如下:
1 | Promise1.prototype.then = function(onFulfilled, onRejected) { |
2.链式回调的返回值处理
- 如果第一个promise返回一个普通值,会走到下一次then成功的回调
- 如果第一个promise返回了一个promise,需要等待返回的promise执行后的结果,再传递到下一次then中,所以用变量x来接收第一个then的返回值
- x可能是普通值,也可能是promise,需要实现一个resolvePromise方法统一处理返回值
更新then方法代码如下:
1 | Promise.prototype.then = function(onFuilled, onRejected) { |
实现兼容多种Promise的resolvePromise函数
1 | /** |
然后需要判断x的类型
1 | if (x instanceof Promise1) { |
这里的代码是完全按照规范实现的。如果 x 为 Promise 的话,需要判断以下几个情况:
- 如果 x 处于等待态,Promise 需保持为等待态直至 x 被执行或拒绝
- 如果 x 处于其他状态,则用相同的值处理 Promise
以上这些是规范需要我们判断的情况,实际上不判断状态也是可行的。
接下来继续按照规范来实现剩余的代码
promise的处理方式是,一旦进入成功态,就成功了,不会再调用reject,反之亦然
这里通过在resolvePromise方法中,加入called 标识,表示已经进入一个resolve或reject;若果called为true,直接返回,如果为false,置为true
1 | let called = false; // 表示是否调用过成功或者失败 |
- 首先创建一个变量 called 用于判断是否已经调用过函数
- 然后判断 x 是否为对象或者函数,如果都不是的话,将 x 传入 resolve 中
- 如果 x 是对象或者函数的话,先把 x.then 赋值给 then,然后判断 then 的类型,如果不是函数类型的话,就将 x 传入 resolve 中
- 如果 then 是函数类型的话,就将 x 作为函数的作用域 this 调用之,并且传递两个回调函数作为参数,第一个参数叫做 resolvePromise ,第二个参数叫做 rejectPromise,两个回调函数都需要判断是否已经执行过函数,然后进行相应的逻辑
- 以上代码在执行的过程中如果抛错了,将错误传入 reject 函数中
3.实现catch
捕获错误的方法,catch相当于一个then调用错误方法
1 | Promise1.prototype.catch = function(callback) { |
4.实现Promise.all
Promise.all可以将多个Promise实例包装成一个新的Promise实例。同时,成功和失败的返回值是不同的,成功的时候返回的是一个结果数组,而失败的时候则返回最先被reject失败状态的值。
all接收一个成员为promise实例的数组,依次执行,并按顺序返回执行结果
当所有promise都执行成功,就进入成功态,有一个执行失败了,就进入失败态
1 | // promises是一个promise数组 |
5.实现Promise.race
参数同all,只要有一个promise成功了,就算成功。如果有一个失败了,就失败了,其他promise继续执行
1 | Promise1.race = funciton (promises) { |
6.实现Promise.resolve/Promise.reject
Promise.resolve 可以理解为 生成一个成功的promise
1 | Promise1.resolve = function (value) { |
Promise.reject 即生成一个失败的promise
1 | Promise1.reject = function (value) { |
Promises/A+规范明确要求回调需要通过异步方式执行,用以保证一致可靠的执行顺序
⚠️ 符合Promise A+ 规范的promise总结:
1 | // 定义三个状态 |
参考文章:
https://juejin.im/post/5ab466a35188257b1c7523d2#heading-1