kangkai124/blog

即将彻底掌握 Promise

Opened this issue · 6 comments

catch 返回的还是一个 Promise 对象,因为后面还可以接着调用 then 方法。

new Promise((resolve, reject) => {
	throw new Error('xxx')
})
.then(r => r)
.catch(e => e)
.then(s => { console.log(s) })

// Error: xxx

作为参数的 Promise 实例,自己定义了catch方法,那么它一旦被rejected,并不会触发Promise.all()的catch方法。

const p1 = new Promise((resolve, reject) => {
  resolve('hello');
})
.then(result => result)
.catch(e => e);

const p2 = new Promise((resolve, reject) => {
  throw new Error('报错了');
})
.then(result => result)
.catch(e => e);

Promise.all([p1, p2])
.then(result => console.log(result))
.catch(e => console.log(e));
// ["hello", Error: 报错了]

上面代码中,p1resolvedp2首先会rejected,但是p2有自己的catch方法,该方法返回的是一个新的 Promise 实例,p2指向的实际上是这个实例。该实例执行完catch方法后,也会变成resolved,导致Promise.all()方法参数里面的两个实例都会resolved,因此会调用then方法指定的回调函数,而不会调用catch方法指定的回调函数。

如果p2没有自己的catch方法,就会调用Promise.all()catch方法。

如何取消一个promise

<button id="start">开始一个promise</button>
<button id="cancel">取消它</button>
let cancelReason = 'cancel'
let controller

function race(p) {
  let obj = {}
  let p1 = new Promise((resolve, reject) => {
    obj.resolve = resolve
    obj.reject = reject
  })
  obj.promise = Promise.race([p, p1])
  return obj
}

document.querySelector('#start').onclick = function () {
  console.log('开始');
  let _promise = new Promise(resolve => {
    setTimeout(() => {
      resolve('hello')
    }, 5000)
  })
  controller = race(_promise)
  controller.promise.then(resolveValue => {
    if (resolveValue === cancelReason) return
    // resolve code
    console.log('resolve! ' + resolveValue)
  }, rejectValue => {
    if (rejectValue === cancelReason) return
    console.log('reject! ' + rejectValue)
  })
}

document.querySelector('#cancel').onclick = function () {
  console.log('取消了')
  controller.resolve(cancelReason)
}

promise解决了什么问题

解决了异步问题,相对于之前的解决方案—回调函数和事件,更加的合理和强大。

promise的缺点

  1. 无法取消 promise
  2. 不设置回调函数的话,内部错误无法被外界捕获
  3. pending状态,无法得知是刚开始还是即将完成

promise的使用

  • new Promise()
  • .then
  • .catch
  • Promise.resolve
  • Promise.reject
  • Promise.all
  • Promise.race

手动实现一个Promise

简版:

const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

class Aromise {
  constructor(fn) {
    this.state = PENDING
    this.value = null
    this.reason = null
    this.onFulfilledCbs = []
    this.onRejectedCbs = []

    const resolve = value => {
      // 使用macro-task机制,确保onFulfilled异步执行,
      // 且在 then 方法被调用的那一轮事件循环之后的新执行栈中执行,
      // 即将then的回调push到onFulfilledCbs后再执行。
      setTimeout(() => {
        if (this.state === PENDING) {
          this.state = FULFILLED
          this.value = value
          this.onFulfilledCbs.forEach(cb => {
            this.value = cb(this.value)
          })
        }
      })
    }

    const reject = reason => {
      setTimeout(() => {
        if (this.state === PENDING) {
          this.state = REJECTED
          this.reason = reason
          this.onRejectedCbs.forEach(cb => {
            this.reason = cb(this.reason)
          })
        }
      })
    }

    try {
      fn(resolve, reject)
    } catch (err) {
      reject(err)
    }
  }

  then (onFulfilled, onRejected) {
    typeof onFulfilled === 'function' && this.onFulfilledCbs.push(onFulfilled)
    typeof onRejected === 'function' && this.onRejectedCbs.push(onRejected)
    return this
  }
}

new Aromise((resolve, reject) => {
  console.log('start');
  setTimeout(() => {
    resolve(223)
  }, 2000)
})
.then(res => {
  console.log(res)
})

Promise.all

如果参数中 promise 有一个失败(rejected),此实例回调失败(reject),失败原因的是第一个失败 promise 的结果。

let t1 = new Promise((resolve,reject)=>{
    resolve("t1-success")
})
let t2 = new Promise((resolve,reject)=>{
    resolve("t2-success")
})
let t3 =Promise.reject("t3-error");
Promise.all([t1,t2,t3]).then(res=>{
    console.log(res)
}).catch(error=>{
    console.log(error)
})
//打印出来是t3-error

定制Error对象

Error 对象是ECMAScript的内建(build in)对象。

但是由于stack trace等原因我们不能完美的创建一个继承自 Error 的类,不过在这里我们的目的只是为了和Error有所区别,我们将创建一个 TimeoutError 类来实现我们的目的。

在ECMAScript6中可以使用 class 语法来定义类之间的继承关系。

class MyError extends Error{
    // 继承了Error类的对象
}

为了让我们的 TimeoutError 能支持类似 error instanceof TimeoutError 的使用方法,我们还需要进行如下工作。

//TimeoutError.js
function copyOwnFrom(target, source) {
    Object.getOwnPropertyNames(source).forEach(function (propName) {
        Object.defineProperty(target, propName, Object.getOwnPropertyDescriptor(source, propName));
    });
    return target;
}
function TimeoutError() {
    var superInstance = Error.apply(null, arguments);
    copyOwnFrom(this, superInstance);
}
TimeoutError.prototype = Object.create(Error.prototype);
TimeoutError.prototype.constructor = TimeoutError;

我们定义了 TimeoutError 类和构造函数,这个类继承了Error的prototype。

它的使用方法和普通的 Error 对象一样,使用 throw 语句即可,如下所示。

var promise = new Promise(function () {
  throw TimeoutError('timeout')
})
promise.catch(function (error) {
  console.log(error instanceof TimeoutError) // true
})

有了这个 TimeoutError 对象,我们就能很容易区分捕获的到底是因为超时而导致的错误,还是其他原因导致的Error对象了。