包教包会,和你完成一个Promise(三)

1、承上

本篇文章是包教包会,和你实现一个Promise的第三篇文章。由于这个小系列旨在完成一个符合规范的Promise,并且这三篇文章有先后关系,若是没有看过前两篇,第三篇看起来会有些莫名其妙。要是没看过,仍是建议先看下前两篇。在这里:javascript

包教包会,和你实现一个Promise(一)java

包教包会,和你实现一个Promise(二)git

到目前为止,咱们的MyPromise虽然能够用了,可是还有一些地方不符合规范,再就是有些地方还有点问题,咱们来进行逐个修改。github

2、then方法完善

2.1 给参数设置默认值

如今then方法的两个参数是没有默认值的,因此若是使用者在调用then方法时,没有传递参数,后面咱们使用onResolved或者onRejected时,程序会报onResolved/onRejected is not a function,这会致使咱们的Promise被迫终止,因此要给它们加上默认值。npm

MyPromise.prototype.then = function (onResolved, onRejected) {
  // 看这里,设置onResovled的默认值
  if (typeof onResolved !== 'function') {
    onResolved = function(value) {
      return value
    }
  }
  // 看这里,设置onRejected的默认值
  if (typeof onRejected !== 'function') {
    onRejected = function(reason) {
      throw reason
    }
  }

  let promise2

  if (this.status === 'pending') {
    promise2 = new MyPromise((resolve, reject) => {
      function successFn(value) {
        let x = onResolved(value)
        resolve_promise(promise2, x, resolve, reject)
      }

      function failFn(reason) {
        let x = onRejected(reason)
        resolve_promise(promise2, x, resolve, reject)
      }

      this.resolvedCallbacks.push(successFn)
      this.rejectedCallbacks.push(failFn)
    })
  }

  if (this.status === 'fulfilled') {
    promise2 = new MyPromise((resolve, reject) => {
      setTimeout(() => {
        let x = onResolved(this.data)
        resolve_promise(promise2, x, resolve, reject)
      })
    })
  }

  if (this.status === 'rejected') {
    promise2 = new MyPromise((resolve, reject) => {
      setTimeout(() => {
        let x = onRejected(this.reason)
        resolve_promise(promise2, x, resolve, reject)
      })
    })
  }

  return promise2
}
复制代码

上面经过tyepof判断onResovled或者onRejected是否是function类型,若是不是,就给一个函数。这样,不只避免了不传值的状况,也解决了使用随便乱传参数的问题。数组

对于onResolved的方法,咱们声明一个函数直接返回value便可,对于onRejected的方法,咱们抛一个错误出来。这样promise2也就能够经过then方法拿到它们返回或者抛出的错误的。promise

2.2 onResolved/onRejected的执行问题

咱们来看这里的代码,这里只是其中一处:bash

if (this.status === 'fulfilled') {
  promise2 = new MyPromise((resolve, reject) => {
    setTimeout(() => {
      // 看这里看这里
      let x = onResolved(this.data)
      resolve_promise(promise2, x, resolve, reject)
    })
  })
}
复制代码

咱们知道,then方法执行时,用户会写两个函数onResovled和onRejected而且传进来,而后咱们调用它们来得到当前Promise实例resolve或者reject传递过来的值。可是问题来了,由于onResovled和onRejected是用户实现、咱们调用的,那用户写onResolved或者onRejected时写错了,里面语法有问题,执行的时候报错了,那咱们的程序也会终止掉。因此咱们要针对这种可能性作一个try...catch捕捉。异步

总共有四个地方须要try...catch:函数

MyPromise.prototype.then = function (onResolved, onRejected) {
  if (typeof onResolved !== 'function') {
    onResolved = function(value) {
      return value
    }
  }
  if (typeof onRejected !== 'function') {
    onRejected = function(reason) {
      throw reason
    }
  }
  let promise2

  if (this.status === 'pending') {
    promise2 = new MyPromise((resolve, reject) => {
      function successFn(value) {
        // 这里要try...catch
        try {
          let x = onResolved(value)
          resolve_promise(promise2, x, resolve, reject)
        } catch (e) {
          reject(e)
        }
      }

      function failFn(reason) {
        // 这里要try...catch
        try {
          let x = onRejected(reason)
          resolve_promise(promise2, x, resolve, reject)
        } catch (e) {
          reject(e)
        }
      }

      this.resolvedCallbacks.push(successFn)
      this.rejectedCallbacks.push(failFn)
    })
  }

  if (this.status === 'fulfilled') {
    promise2 = new MyPromise((resolve, reject) => {
      setTimeout(() => {
        // 这里也要哦
        try {
          let x = onResolved(this.data)
          resolve_promise(promise2, x, resolve, reject)
        } catch (e) {
          reject(e)
        }
      })
    })
  }

  if (this.status === 'rejected') {
    promise2 = new MyPromise((resolve, reject) => {
      setTimeout(() => {
        // 这里也要哦
        try {
          let x = onRejected(this.reason)
          resolve_promise(promise2, x, resolve, reject)
        } catch (e) {
          reject(e)
        }
      })
    })
  }

  return promise2
}
复制代码

总共四个地方,若是onResolved或者onRejected执行报错,会被catch到,而且直接用reject把promise2标记为失败状态。then方法改完了,下面构造函数也要小小改一下。

3、构造函数完善

构造函数有个地方要修改:resolve和reject里的内容须要异步执行。 为啥要这样写,其实我也不清楚,由于从逻辑上来看即便不写彷佛也没毛病。可是不它们设置成异步执行的话,有6个测试用例通不过。

因此我去看了它的测试用例,你也能够看看,地址在这里 :它是这么测的:

specify("fulfilled after a delay", function (done) {
  var d = deferred();
  var isFulfilled = false;

  d.promise.then(function onFulfilled() {
    assert.strictEqual(isFulfilled, true);
    done();
  });

  setTimeout(function () {
    d.resolve(dummy);
    isFulfilled = true;
  }, 50);
});
复制代码

根据规范,我只要保证onResolved在resolve以后执行就行,因此如今写的代码没啥问题,但问题是它的测试用例,先声明了一个isFulfilled,而后使用setTimeout调用resolve并将isFulfilled置为true,并在onResolved判断一个isFulfilled为true。

可是,咱们是在resolve函数里面就已经调用resolve了,因此此时isFufilled仍是false,因此咱们须要给resolve和reject里的代码都异步执行才能经过这里的测试。

function MyPromise(executor) {
  this.status = 'pending'
  this.data = undefined
  this.reason = undefined
  this.resolvedCallbacks = []
  this.rejectedCallbacks = []

  let resolve = (value) => {
    // 这里加个setTimeout
    setTimeout(() => {
      if (this.status === 'pending') {
        this.status = 'fulfilled'
        this.data = value
        this.resolvedCallbacks.forEach(fn => fn(this.data))
      }
    })
  }
  let reject = (reason) => {
    // 这里也加个setTimeout
    setTimeout(() => {
      if (this.status === 'pending') {
        this.status = 'rejected'
        this.reason = reason
        this.rejectedCallbacks.forEach(fn => fn(this.reason))
      }
    })
  }
  executor(resolve, reject)
}
复制代码

4、测试

测试很简单,有专门的测试工具。

npm install -g promises-aplus-tests
复制代码

同时还要将咱们的MyPromise暴露出去,并提供promise实例和resolve以及reject函数引用。

MyPromise.deferred = function() {
  let dfd = {}
  dfd.promise = new MyPromise(function(resolve, reject) {
    dfd.resolve = resolve
    dfd.reject = reject
  })
  return dfd
}

try {
  module.exports = MyPromise
} catch (e) {}
复制代码

咱们须要在MyPromise上写一个静态方法,执行后返回一个dfd对象。这个对象上有三个属性:

  • promise: 一个咱们要测试的Promise实例
  • resolve: 这个实例的resolve函数
  • reject: 这个实例的reject函数

好了,到这里看一下包括测试须要在内的全部的代码:

function MyPromise(executor) {
  this.status = 'pending'
  this.data = undefined
  this.reason = undefined
  this.resolvedCallbacks = []
  this.rejectedCallbacks = []

  let resolve = (value) => {
    setTimeout(() => {
      if (this.status === 'pending') {
        this.status = 'fulfilled'
        this.data = value
        this.resolvedCallbacks.forEach(fn => fn(this.data))
      }
    })
  }
  let reject = (reason) => {
    setTimeout(() => {
      if (this.status === 'pending') {
        this.status = 'rejected'
        this.reason = reason
        this.rejectedCallbacks.forEach(fn => fn(this.reason))
      }
    })
  }
  executor(resolve, reject)
}

MyPromise.prototype.then = function (onResolved, onRejected) {
  if (typeof onResolved !== 'function') {
    onResolved = function(value) {
      return value
    }
  }

  if (typeof onRejected !== 'function') {
    onRejected = function(reason) {
      throw reason
    }
  }

  let promise2

  if (this.status === 'pending') {
    promise2 = new MyPromise((resolve, reject) => {
      function successFn(value) {
        try {
          let x = onResolved(value)
          resolve_promise(promise2, x, resolve, reject)
        } catch (e) {
          reject(e)
        }
      }

      function failFn(reason) {
        try {
          let x = onRejected(reason)
          resolve_promise(promise2, x, resolve, reject)
        } catch (e) {
          reject(e)
        }
      }

      this.resolvedCallbacks.push(successFn)
      this.rejectedCallbacks.push(failFn)
    })
  }

  if (this.status === 'fulfilled') {
    promise2 = new MyPromise((resolve, reject) => {
      setTimeout(() => {
        try {
          let x = onResolved(this.data)
          resolve_promise(promise2, x, resolve, reject)
        } catch (e) {
          reject(e)
        }
      })
    })
  }

  if (this.status === 'rejected') {
    promise2 = new MyPromise((resolve, reject) => {
      setTimeout(() => {
        try {
          let x = onRejected(this.reason)
          resolve_promise(promise2, x, resolve, reject)
        } catch (e) {
          reject(e)
        }
      })
    })
  }

  return promise2
}

function resolve_promise(promise2, x, resolve, reject) {
  if (x === promise2) {
    reject(new TypeError('Chaining cycle detected for promise'))
    return
  }
  if (x instanceof MyPromise) {
    x.then(function(v) {
      resolve_promise(promise2, v, resolve, reject)
    }, function(t) {
      reject(t)
    })
    return
  }
  if (x !== null && (typeof x === 'function' || typeof x === 'object')) {
    let called = false
    try {
      let then = x.then
      if (typeof then === 'function') {
        then.call(x, function resolvePromise(y) {
          if (called) return
          called = true
          resolve_promise(promise2, y, resolve, reject)
        }, function rejectPromise(r) {
          if (called) return
          called = true
          reject(r)
        })
      } else {
        resolve(x)
      }
    } catch (e) {
      if (called) return
      called = true
      reject(e)
    }
  } else {
    resolve(x)
  }
}

MyPromise.deferred = function() {
  let dfd = {}
  dfd.promise = new MyPromise(function(resolve, reject) {
    dfd.resolve = resolve
    dfd.reject = reject
  })
  return dfd
}

try {
  module.exports = MyPromise
} catch (e) {

}
复制代码

开始测试:

promises-aplus-tests MyPromise.js
复制代码

你将会看到:

测试成功

872个测试用例所有经过!

5、锦上添花

接下来,咱们实现一些很是经常使用的方法,这些方法虽然不在规范里,可是它们平时也有用武之地!

5.1 MyPromise.all()

这个方法有如下特色:

  • 参数:是一个MyPromise实例组成的数组
  • 返回一个MyPromise实例,不过要等到全部的参数里全部的promise状态肯定
  • 参数的实例中,只要有一个是reject,返回的promise就是reject状态
MyPromise.all = function(arr) {
  return new MyPromise(function(resolve, reject) {
    let result = new Array(arr.length)
    let count = 0
    for(let i=0; i<arr.length; ++i) {
      let currentPromise = arr[i]
      currentPromise.then(function(res) {
        result[i] = res
        count++
        if (count === arr.length) {
          resolve(result)
        }
      }, function(reason) {
        reject(reason)
      })
    }
  }) 
}
复制代码

很好理解,就很少讲了~

5.2 catch()方法

注意,这个方法是实例方法:

  • 接收一个函数做为参数
  • 当then出现问题时会执行这个函数
MyPromise.prototype.catch = function(failFn) {
  this.then(null, function(reason) {
    failFn(reason)
  })
}
复制代码

很少说了

5.3 race()方法

注意,这是一个静态方法:

  • 接收一个promise实例构成的数组,返回一个MyPromise实例
  • 当参数里的promise第一个状态确认时,把它做为成功或者失败的值返回一个新实例
MyPromise.race = function(arr) {
  return new MyPromise(function(resolve, reject) {
    arr.forEach(promise => {
      promise.then(resolve, reject)
    })
  })
}
复制代码

完事~

5.4 MyPromise.resolve()

直接返回一个成功状态的Promise,静态方法:

MyPromise.resolve = function(value) {
  return new MyPromise(function(resolve, reject) {
    resolve(value)
  })
}
复制代码

5.5 MyPromise.reject()

直接返回一个失败状态的Promise实例,静态方法:

MyPromise.reject = function(reason) {
  return new MyPromise(function(resolve, reject) {
    reject(reason)
  })
}
复制代码

后面咱们实现的这些方法是没有测试用例的哦~

6、起一个帅气的名字

好不容易完成了一个彻底符合规范的Promise,给它起一个帅气的名字吧,就叫Zoro吧。Zoro是《海贼王》里罗罗诺亚·索隆的英文名字,索隆在小时候曾经向古伊娜许诺要成为世界最强剑豪 ,而如今索隆刚拿到阎魔这把刀,在成为最强的道路上一路狂奔,因此我以为这个名字再合适不过了!

zoro

完成的zoro地址在这里 ,欢迎点赞,感谢您的阅读!

(完)

相关文章
相关标签/搜索