阅读 827
手写promise,新手需要沉下心来看看(二)

手写promise,新手需要沉下心来看看(二)

实现then的链式调用

我们接着上一篇讲
上一篇最终代码如下:

详细代码点击我展开 👈
const PENDING = 'pending'; // 等待
const FULFILLED = 'fulfilled'; // 成功
const REJECTED = 'rejected'; // 失败
class MyPromise {
  status = PENDING
  value = undefined
  reason = undefined
  successCallback = []
  failCallback = []
  constructor(executor) {
    executor(this.resolve, this.reject)
  }
  resolve = value => {
    if (this.status !== PENDING) return;
    this.status = FULFILLED;
    this.value = value
    while (this.successCallback.length) this.successCallback.shift()(value)
  }
  reject = reason => {
    if (this.status !== PENDING) return;
    this.status = REJECTED;
    this.reason = reason
    while (this.failCallback.length) this.failCallback.shift()(reason)
  }
  then(successCallback, failCallback){
    if (this.status === FULFILLED) {
      successCallback(this.value)
    }
    else if (this.status === REJECTED) failCallback(this.reason)
    else {
      this.successCallback.push(successCallback)
      this.failCallback.push(failCallback)
    }
  }
}
复制代码

接下来的目标是实现 then的链式调用,首先看一个示例

let promise = new Promise((resolve, reject) => {
  resolve('成功')
})
promise.then(value => {
  console.log(value) // 成功
  return '谁偷了我的奶酪'
})
  .then(res => {
    console.log(res) // 谁偷了我的奶酪
    return 4
  })
复制代码

第一个then 返回了一个 字符串, 然后接着可以 往后 .then 获取上一个then返回的值 上一个then的值是哪里来的呢,我们看源码中 是不是 successCallback(this.value) 这个返回的值, 那么 then 返回的 肯定是一个新的 promise, 不然也无法 再次 .then,所以我们 大概有了一个思路

...
class MyPromise {
  ...
  then(successCallback, failCallback) {
    let promise2 = new MyPromise((resolve, reject) => {
      if (this.status === FULFILLED) {
       let res = successCallback(this.value)
       resolve(res)
      }
     ...
    })

    return promise2
  }
}
复制代码

在代码中 我 new 了 MyPromise 并将之返回, 这样 then 就返回了一个 promise, 那么 then 的返回值如何拿到呢? 通过 resolve(successCallback(this.value)),下一个then就可以拿到值了. 这样我们将上面的示例中 的 Promise 换成 MyPromise 也可以成功了. 不过,这是最简单的情况, then 的返回值有很多情况,我们需要一一来处理.

  1. then 返回普通值
  2. then 返回一个 promise
  3. then 返回了自身的promise
  4. then 里面传的不是一个函数

then 返回普通值 或 promise

then返回普通值按照上面的处理方法就可以完成,不过为了处理后面几种问题,我们需要创建一个方法专门处理这几种情况

...
class MyPromise {
  ...
  then(successCallback, failCallback) {
    let promise2 = new MyPromise((resolve, reject) => {
      if (this.status === FULFILLED) {
       let res = successCallback(this.value)
       resolvePromise(res, resolve, reject)
      }
     ...
    })

    return promise2
  }
}
function resolvePromise(res, resolve, reject) {
  // then 返回的是 promise
  if (res instanceof MyPromise) {
    res.then(value => resolve(value), reason => reject(reason))
  }
  else {
    // then返回的是普通值
    resolve(res)
  }
}
// 测试
let promise = new MyPromise((resolve, reject) => {
  resolve('成功')
})
promise.then(value => {
  console.log(value) // 成功
  // 返回一个 promise
  return new MyPromise((resolve, reject) => {resolve('newPromise')})
})
  .then(res => {
    console.log(res) // newPromise
    return 4
  })
复制代码

在上图中,我们定一个了 resolvePromise 返回, 接受了上面的三个参数, 如果是普通值, 直接 调用 resolve 将 then 返回的普通值传递给下一个promise的then, 如果 then 返回的是 promise,通过调用 这个 promise的then 得到它返回的值, 不管是成功还是失败,我们都要一直往下传,所以目前我们已经解决两种 promise 返回值的情况

then 返回了自身的promise

首先我们看用原生promise调用的一个示例

// 循环调用本身
let p = new Promise((resolve, reject) => {
  resolve(9)
})
let k = p.then(res => {
  console.log(res)
  return k
})
//报错: TypeError: Chaining cycle detected for promise #<Promise>
复制代码

这里我们看到 我们定义了一个 k 表示 p.then 返回的 promise, 可是 我们在这个 then 里面却 返回了 k, 所以导致 调用本身产生了循环调用,接下来我们来处理这种情况

...
class MyPromise {
  ...
  then(successCallback, failCallback) {
    let promise2 = new MyPromise((resolve, reject) => {
      if (this.status === FULFILLED) {
       setTimeout(() => {
        let res = successCallback(this.value)
        resolvePromise(res, resolve, reject, promise2);
       }, 0);
      }
     ...
    })

    return promise2
  }
}
function resolvePromise(res, resolve, reject, promise2) {
 // 如果 上一个 then 返回的 res 与 then要传递的 promise相同说明 产生循环调用
 if (promsie2 === res) {
 // 将 错误传递给下个then 的 reject
    return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
  }
 ...
}

// 循环调用本身
let p = new MyPromise((resolve, reject) => {
  resolve(9)
})
let k = p.then(res => {
  console.log(res)
  return k
})
// 在 后面一个then可以获取到报错信息
k.then(res => {}, reason => console.log(reason)) // Chaining cycle detected for promise #<Promise>
复制代码

这里我们给 resolvePromise 传递了一个新参数 promise2, 这个 promise2 是 then将要传递给下一个promise的, 然后在这个方法里面 我们通过 res(上个then返回值)与 promise2 比较,如果相等,则循环调用,通过 reject将循环报错信息传递给下个 promise 的 reject. 在代码中的测试我们可以看到确实获取到了报错信息, 原生的 Promise 同样也是这么做的.
上面有一个注意点: 我给那个函数加了一个 settimeout 为0, 这么做的原因是 promise2 还没有初始化完我们就拿来用会导致 报错, 所以给他加个异步机制, 这样传递就不会有问题了.

then传递的不是一个函数

 then(successCallback, failCallback){
      successCallback = Object.prototype.toString.call(successCallback)==='[object Function]' ? successCallback : v => v;
    failCallback = Object.prototype.toString.call(failCallback)==='[object Function]' ? failCallback : r => {throw r};
     ...
    })
复制代码

我们只需要在then的头部增加这么两行代码, 判断 successCallback是不是函数, 这样 then 即使传递了一个普通值也只会将 前面的值传递 给下一个then,忽略这次then的执行 例如:

let  p = new MyPromise((resolve, reject) => {
  resolve(1)
})
p.then('ye ye ye')
.then('sss')
.then(res => console.log(res, 'res'), // 输出 1 res
reason => console.log(reason, 'reason'))
复制代码

捕获错误信息

1 如果我们在 new Promise 里面写了一个错误代码, 那么我们应该将之捕获传递给第一个then中的 reject.

let p = new MyPromise((resolve, reject) => {
  throw new Error('代码错误')
})
p.then(res => {}, reason => {console.log(reason)}) // 代码错误
复制代码

具体的代码也很简单, 只需要给 构造函数中的执行器包裹一层 try catch就行

  constructor(executor) {
    try {
      executor(this.resolve, this.reject)
    } catch (e) {
      this.reject(e)
    }
  }
复制代码

2 如果在 promise.then中报错,同样的方式 try catch 捕获,传递给下一个then的 reject.

  let promise2 = new MyPromise((resolve, reject) => {
      if (this.status === FULFILLED) {
        setTimeout(() => {
          try {
            let res = successCallback(this.value)
            resolvePromise(res, resolve, reject, promise2)
          } catch (e) {
            reject(e)
          }
        }, 0);
      }
复制代码

处理 异步时候 then 链式调用

在上面的代码中我们发现其实都是在同步的状态处理 resolve, 我们先把 reject 处理一下, 处理方法和 resolve 一样

  else if (this.status === REJECTED) {
        setTimeout(() => {
          try {
            let res = failCallback(this.reason)
            resolvePromise(res, resolve, reject, promise2)
          } catch (e) {
            reject(e)
          }
        }, 0);
      }
复制代码

只需要将 失败回调的值传递给 resolvePromise就完成了.现在还剩下异步的没有处理,首先我们看到处理异步的代码如下, 要将之改为 能够链式调用

 else {
        this.successCallback.push(successCallback)
        this.failCallback.push(failCallback)
      }
复制代码

解决方法如下

      else {
        this.successCallback.push(() => {
          setTimeout(() => {
            try {
              let res = successCallback(this.value)
              resolvePromise(res, resolve, reject, promise2)
            } catch (e) {
              reject(e)
            }
          }, 0);
        })
        this.failCallback.push(() => {
          setTimeout(() => {
            try {
              let res = failCallback(this.reason)
              resolvePromise(res, resolve, reject, promise2)
            } catch (e) {
              reject(e)
            }
          }, 0);
        })
      }
复制代码

将 successCallback 包裹在一个函数中,然后复制 同步中的代码即可

 resolve = value => {
   
  // while(this.successCallback.length) this.successCallback.shift()(value)
  while(this.successCallback.length) this.successCallback.shift()()
  }
复制代码

最后只要将 resolve 和 reject 中传递的 value 和 reason 删除即可.这样 then的链式调用基本完成.

完整代码点击我展开 👈
const PENDING = 'pending'; // 等待
const FULFILLED = 'fulfilled'; // 成功
const REJECTED = 'rejected'; // 失败
class MyPromise {
  status = PENDING
  value = undefined
  reason = undefined
  successCallback = []
  failCallback = []
  constructor(executor) {
    try {
      executor(this.resolve, this.reject)
    } catch (e) {
      this.reject(e)
    }
  }
  resolve = value => {
    if (this.status !== PENDING) return;
    this.status = FULFILLED;
    this.value = value
    while (this.successCallback.length) this.successCallback.shift()()
  }
  reject = reason => {
    if (this.status !== PENDING) return;
    this.status = REJECTED;
    this.reason = reason
    while (this.failCallback.length) this.failCallback.shift()()
  }
  then = (successCallback, failCallback) => {
   successCallback = Object.prototype.toString.call(successCallback)==='[object Function]' ? successCallback : v => v;
    failCallback = Object.prototype.toString.call(failCallback)==='[object Function]' ? failCallback : r => {throw r};
    let promise2 = new MyPromise((resolve, reject) => {
      if (this.status === FULFILLED) {
        setTimeout(() => {
          try {
            let res = successCallback(this.value)
            resolvePromise(res, resolve, reject, promise2)
          } catch (e) {
            reject(e)
          }
        }, 0);
      }
      else if (this.status === REJECTED) {
        setTimeout(() => {
          try {
            let res = failCallback(this.reason)
            resolvePromise(res, resolve, reject, promise2)
          } catch (e) {
            reject(e)
          }
        }, 0);
      }
      else {
        this.successCallback.push(() => {
          setTimeout(() => {
            try {
              let res = successCallback(this.value)
              resolvePromise(res, resolve, reject, promise2)
            } catch (e) {
              reject(e)
            }
          }, 0);
        })
        this.failCallback.push(() => {
          setTimeout(() => {
            try {
              let res = failCallback(this.reason)
              resolvePromise(res, resolve, reject, promise2)
            } catch (e) {
              reject(e)
            }
          }, 0);
        })
      }
    })

    return promise2
  }
}
function resolvePromise(res, resolve, reject, promise2) {
  if (promise2 === res) {
    return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
  }
  // then 返回的是 promise
  if (res instanceof MyPromise) {
    res.then(value => resolve(value), reason => reject(reason))
  }
  else {
    // then返回的是普通值
    resolve(res)
  }
}
复制代码

实现静态方法 MyPromsie.resolve

看看如何使用

let p = Promise.resolve(1).then(res => {console.log(res)}) // 1
let k = Promise.resolve(new Promise((resolve) => {
  resolve(333)
})).then(res => console.log(res)) // 333
复制代码

我们需要处理这两种情况, 实现如下

 static resolve(value) {
    // 如果本身就是 promise,只需要直接返回
    if(value instanceof MyPromise) return value
    // 如果是一个值,将值包裹在 promise中返回
   return new MyPromise((resolve) => resolve(value))
  }
复制代码

all 的实现

mdn 介绍: Promise.all() 方法接收一个promise的iterable类型(注:Array,Map,Set都属于ES6的iterable类型)的输入,并且只返回一个Promise实例, 那个输入的所有promise的resolve回调的结果是一个数组。这个Promise的resolve回调执行是在所有输入的promise的resolve回调都结束,或者输入的iterable里没有promise了的时候。它的reject回调执行是,只要任何一个输入的promise的reject回调执行或者输入不合法的promise就会立即抛出错误,并且reject的是第一个抛出的错误信息。 案例:

const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve, reject) => {
  setTimeout(resolve, 100, 'foo');
});

Promise.all([promise1, promise2, promise3]).then((values) => {
  console.log(values);
});
// expected output: Array [3, 42, "foo"]
复制代码

接下来就是实现部分, 首先我们看看有点问题的 all

  static all = arr => {
    // 结果数组
    let ans = []
    // 不管怎样, all返回的都是 一个 promsie实例
    return new MyPromise((resolve, reject) => {
    // 遍历 传入的数组
      arr.forEach(current => {
      // 如果是一个 promise
        if(current instanceof MyPromise) {
        // 调用 then获取它实际的 值,如果是成功的就会 push进数组,
        // 如果是错误的,会直接 reject,这个时候promise状态已经变为 rejected,不会再变了
          current.then(res => {
            ans.push(res)
            //只有长度相等才会 resolve
            if(ans.length === arr.length) resolve(ans)
          },reason => reject(reason))
        }
        else {
          ans.push(current)
        }
      });
    })
  }
}
复制代码

首先 all 返回的是一个 promise, 所以直接返回 new MyPromise,然后就是遍历数组, 有两种情况,如果是一个 promise,我们会 分别拿 resolve和reject值, 如果是 resolve,会推入结果数组,当结果数组长度与输入的数组长度相等就 resolve(ans),不然期间有一个reject,会直接 reject一个错误出去.

const promise1 = MyPromise.resolve(3);
const promise2 = 42;
const promise3 = new MyPromise((resolve, reject) => {
  setTimeout(resolve, 100, 'foo');
});

MyPromise.all([promise1, promise2, promise3]).then((values) => {
  console.log(values);
});
// expected output: Array [3, 42, "foo"]
// 实际输出: [ 42, 3, 'foo' ]
复制代码

mdn中有这么一句话 返回值将会按照参数内的 promise 顺序排列,而不是由调用 promise 的完成顺序决定。 我们的代码其实没有按照顺序,考虑到异步,推入的顺序确实有可能是不同的 image.png 解决办法

  static all = arr => {
    // 结果数组
    let ans = []
    // 不管怎样, all返回的都是 一个 promsie实例
    return new MyPromise((resolve, reject) => {
      function addData(key, value) {
        ans[key] = value
        if (ans.length === arr.length) resolve(ans)
      }
      arr.forEach((current, i) => {
        if (current instanceof MyPromise) {
          current.then((res) => {
            addData(i, res)
          }, reason => reject(reason))
        }
        else {
          addData(i, current)
        }
      });
    });
  }
}
复制代码

我们将值添加到数组的时候不用 push, 而是用 key, value的形式,这样就可以强制让每一个 promise在其适当的位置了.

catch实现

终于到了最后一个api的实现,我们知道 promise不仅仅可以用 then的第二个参数捕获错误,也可以直接通过在.then后面直接 .catch捕获错误,这样的好处是如果有多个then,我们只要在最后一个 then后面catch就行, 而catch原理也很简单, 就是类似于 then(undefind,failCallback),为什么这样可以捕获呢,因为 多个then链式调用的时候,只要有个then出错,错误就会一直传递到最后一个then中的 失败回调.
实现原理如下:

catch (failCallback) {
    return this.then(undefined, failCallback)
  }
复制代码

最后奉上完整代码

详细代码点击我展开 👈
const PENDING = 'pending'; // 等待
const FULFILLED = 'fulfilled'; // 成功
const REJECTED = 'rejected'; // 失败
class MyPromise {
  status = PENDING
  value = undefined
  reason = undefined
  successCallback = []
  failCallback = []
  constructor(executor) {
    try {
      executor(this.resolve, this.reject)
    } catch (e) {
      this.reject(e)
    }
  }
  resolve = value => {
    if (this.status !== PENDING) return;
    this.status = FULFILLED;
    this.value = value
    while (this.successCallback.length) this.successCallback.shift()()
  }
  reject = reason => {
    if (this.status !== PENDING) return;
    this.status = REJECTED;
    this.reason = reason
    while (this.failCallback.length) this.failCallback.shift()()
  }
  then = (successCallback, failCallback) => {
   successCallback = Object.prototype.toString.call(successCallback)==='[object Function]' ? successCallback : v => v;
    failCallback = Object.prototype.toString.call(failCallback)==='[object Function]' ? failCallback : r => {throw r};
    let promise2 = new MyPromise((resolve, reject) => {
      if (this.status === FULFILLED) {
        setTimeout(() => {
          try {
            let res = successCallback(this.value)
            resolvePromise(res, resolve, reject, promise2)
          } catch (e) {
            reject(e)
          }
        }, 0);
      }
      else if (this.status === REJECTED) {
        setTimeout(() => {
          try {
            let res = failCallback(this.reason)
            resolvePromise(res, resolve, reject, promise2)
          } catch (e) {
            reject(e)
          }
        }, 0);
      }
      else {
        this.successCallback.push(() => {
          setTimeout(() => {
            try {
              let res = successCallback(this.value)
              resolvePromise(res, resolve, reject, promise2)
            } catch (e) {
              reject(e)
            }
          }, 0);
        })
        this.failCallback.push(() => {
          setTimeout(() => {
            try {
              let res = failCallback(this.reason)
              resolvePromise(res, resolve, reject, promise2)
            } catch (e) {
              reject(e)
            }
          }, 0);
        })
      }
    })

    return promise2
  }
  catch (failCallback) {
    return this.then(undefined, failCallback)
  }
  static resolve(value) {
    // 如果本身就是 promise,只需要直接返回
    if (value instanceof MyPromise) return value
    // 如果是一个值,将值包裹在 promise中返回
    return new MyPromise((resolve) => resolve(value))
  }
  static all = arr => {
    // 结果数组
    let ans = []

    // 返回一个 promsie实例
    return new MyPromise((resolve, reject) => {
      function addData(key, value) {
        ans[key] = value
        if (ans.length === arr.length) resolve(ans)
      }
      arr.forEach((current, i) => {
        if (current instanceof MyPromise) {
          current.then((res) => {
            addData(i, res)
          }, reason => reject(reason))
        }
        else {
          addData(i, current)
        }
      });
    })
  }
}
function resolvePromise(res, resolve, reject, promise2) {
  if (promise2 === res) {
    return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
  }
  // then 返回的是 promise
  if (res instanceof MyPromise) {
    res.then(value => resolve(value), reason => reject(reason))
  }
  else {
    // then返回的是普通值
    resolve(res)
  }
}
复制代码

总结

前端学习之 路漫漫其修远兮,吾将上下而求索!!!

文章分类
前端
文章标签