Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

即将彻底掌握 Promise #25

Open
kangkai124 opened this issue May 30, 2019 · 6 comments
Open

即将彻底掌握 Promise #25

kangkai124 opened this issue May 30, 2019 · 6 comments
Labels
dtkb don't know before JS

Comments

@kangkai124
Copy link
Owner

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

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

// Error: xxx
@kangkai124
Copy link
Owner Author

作为参数的 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方法。

@kangkai124 kangkai124 added JS dtkb don't know before labels May 30, 2019
@kangkai124
Copy link
Owner Author

如何取消一个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)
}

@kangkai124
Copy link
Owner Author

kangkai124 commented Jul 26, 2019

promise解决了什么问题

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

promise的缺点

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

promise的使用

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

@kangkai124
Copy link
Owner Author

手动实现一个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)
})

@kangkai124
Copy link
Owner Author

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

@kangkai124
Copy link
Owner Author

定制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对象了。

@kangkai124 kangkai124 changed the title promise 即将彻底掌握 Promise Aug 28, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
dtkb don't know before JS
Projects
None yet
Development

No branches or pull requests

1 participant