异步解决方案一:promise

2,251 阅读9分钟

之前一直对于promise,就是一顿用,从来没有深究其内部原理,无意间看到一些关于promise输出顺序的题目也是一顿瞎猜。。。哈哈,在我的同事对promise进行分享之后(虽然我因为起不来没有听),但是学习promise的意识就此种下。。。啥也不说了,就是学。。。

带着疑问去学习:

  1. promise是干什么的?
  2. promise有哪些方法?
  3. pomise如何实现链式调用?之前jquery也是可以链式调用,他们之前的区别?
  4. promise的then方法是如何确定在前面resolve或者reject执行完拿到结果再执行的?
  5. 手写一个满足promiseA+的promise方法(写了一下,发现无法通过promiseA+的测试用例,已放弃,拿着别人写的仔细看了一下)

promise是干什么的

promise是一种异步编程的解决方案,它可以实现把异步操作按照同步操作的流程表达出来。它可以接收一个异步请求,然后在其then方法可以得到前面异步请求的结果进行后续操作,使用链式调用的方式将异步请求的结果按照同步的方式表达出来。

理解误区

1. promise只是异步编程的解决方案,自身并不是异步请求,在new Promise()内部传的方法会立即执行。

const promise = new Promise(function (resolve, reject) {
  console.log('12')
  setTimeout(function () {
    resolve('success')
  }, 1000)
})
promise.then(function (success) {
  console.log(success)
})
// 执行结果: 先输出12,过了一秒后再输入success

2. new Promise内的resolve是异步执行的,且所有then方法和catch方法也会将在当前脚本所有同步任务执行完才会执行

const myPromise = new Promise(function (resolve, reject) {
  console.log('1')
  resolve('2')
  console.log('3')
})
myPromise.then(function (success) {
  console.log(success)
})
setTimeout (function () {
    // 这里可以看到前面myPromise已经resolved了,但是他还是会在最后执行,因为then内部也是异步操作
    myPromise.then(function (success) {
        console.log('again', success)
    })
    console.log('4')
}, 1000)
console.log('5')
// 执行结果是: 1 3 5 2 4 again 2

promise有哪些方法

基本用法

const promise = new Promise(function(resolve, reject) {
  if (/* 异步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});
promise.then(function(value) {}, function (error) {})

基本用法简单概括一下:

  1. 三种状态: - pending(进行中) - fulfilled(已成功) -rejected(已失败)
  2. new Promise传进的方法内resolve返回的是成功的结果,reject返回的是失败的结果
  3. new Promise实例化后的then方法接受两个回调函数,分别是resolve和reject的回调函数

挂载在原型上的方法

Promise.prototype.then()&&Promise.prototype.catch()

  • then: 该方法内的函数在Promise状态为resolve的情况下执行,且then方法内属于异步操作,如果前面的new Promise没有resolve,then内部传进来的函数不执行
  • catch:该方法内的函数在Promise状态为reject的情况下执行,且catch方法内属于异步操作,catch方法和then不太一样,它在前面new Promise()出错或者reject时,catch内部的方法都会执行。

then()和catch()默认返回的都是一个Promise实例对象,即使return了别的信息,返回的仍然是Promise实例对象,还是且这个promise的状态都是resolve

Promise.prototype.finally()

不管Promise对象最后状态如何,finally方法都会执行,(promise是pedding状态不会执行finally方法,resolve和reject都会执行)

挂载在Promise对象上的方法

Promise.resolve() && Promise.reject()

将现有对象转换为Promise对象

Promise.all()

将多个Promise实例包装成一个新的Promise实例。

var p1 = new Promise((resolve) => {
  setTimeout(() => {
    resolve('p1')
  }, 1000)
})
var p2 = new Promise((resolve) => {
  setTimeout(() => {
    resolve('p2')
  }, 2000)
})
var p3 = new Promise((resolve) => {
  setTimeout(() => {
    resolve('p3')
  }, 3000)
})
var p = Promise.all([p1, p2, p3]).then((value) => {
  console.log(value)
}).catch((err) => {
  console.log(err)
})
// 返回值:['p1', 'p2', 'p3']

这里Promise的状态是由p1,p2,p3共同决定的:

  1. p1,p2和p3的状态都为resolve时,p1,p2和p3的返回值都以一个数组的形式传给后续p的then方法里
  2. 其中有一个的状态变为reject时,第一个被reject的实例的返回值会传递给后续p的catch方法里
  3. 如果前面作为参数传进来的promise实例中状态为rejected了,且其自己定义了catch方法,那么后续不会触发Promise.all()的then方法
  4. 如果前面作为参数传进来的promise实例有状态为rejected,且其有catch方法,那么就不会出发后面的Promise.all()的catch方法,会触发then()方法。

Promise.race()

将现有对象转换为Promise对象,只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。

pomise如何实现链式调用?之前jquery也是可以链式调用,他们之前的区别?

promise如何实现链式调用

在每一次执行Promise的then或者catch方法后,返回的都是一个新的Promise实例化对象,使得又一次可以使用then或者catch方法,并且可以拿到上一次then方法return回来的值。

例如:

then内部的函数return了一个数值a。我们发现then方法执行完,返回的是一个新的promise实例,其promisevalue为a

根据以上铺垫,我们就知道,其链式调用就是通过then和catch方法内部return new Promise()

jquery如何实现链式调用

通过return this,在其原型方法执行结束都会teturn其本身

var Jquery = function () {

}
Jquery.prototype = {
  css: function (attr, data) {
    return this
  },
  hide: function () {
    console.log('hide')
    return this
  }
}
var jq = new Jquery()
jq.css().hide()

这里jq调用了css方法,首先会在jq上找css方法,没有找到,然后就去其构造函数Jquery的原型中找,找到css方法并执行,在原型css方法里return this中的this指向的是jq

两者区别

promise执行了then或者catch方法后,每一次返回的是一个新的Promise实例,jquey一直返回的都是其本身

promise的then方法是如何确定在前面resolve或者reject执行完拿到结果再执行的

这里学习了一下别人写的promise,通过这个学习到了一些,中间关于这部分的代码

const PENDING = 'pending'
const RESOLVED = 'resolved'
const REJECTED = 'rejected'
// promise 对象上有resolve,reject,all,race等方法,原型上有then,catch,finally方法
class myPromise {
  constructor (fn) {
    // 首先需要判断fn是否是一个函数
    if (typeof fn != 'function') return 'fn is not function'
    this.PromiseStatus = PENDING
    this.PromiseValue = null
    this.PromiseReason = null
    this.onResolvedCallbacks = []
    this.onRejectedCallbacks = []
    const resolve = value => { // 代码块一
      if(value instanceof Promise) {
        return value.then(resolve, reject);
      }
      // new Promise构造函数传进来的函数fn会立即执行,但是在new Promise内resolve和reject是异步操作(如何做到异步操作 =>使用setTimeout确保new Promise后的then和catch方法会在fn函数执行完才触发)
      setTimeout(() => {
        if (this.PromiseStatus == PENDING) {
          this.PromiseStatus = RESOLVED
          this.PromiseValue = value
          this.onResolvedCallbacks.map(cb => {
            this.PromiseValue = cb(this.PromiseValue)
          })
        }
      })
    }
    const reject = value => {
      setTimeout(() => {
        if (this.PromiseStatus == PENDING) {
          this.PromiseStatus = REJECTED
          this.PromiseReason = value
          this.onRejectedCallbacks.map(cb => {
            this.PromiseReason = cb(this.PromiseReason)
          })
        }
      })
    } 
    try { // 代码块二
      // 立即执行new Promise内的
      fn(resolve, reject)
    } catch (err) {
      reject(err)
    }
  }
  then (onResolved, onRejected) { // 代码块三
    onResolved = typeof onResolved === 'function' ? onResolved : function (v) { return v}
    onResolved = typeof onRejected === 'function' ? onRejected : function (v) { return v}
    console.log('this.PromiseStatus', this.PromiseStatus)
    if (this.PromiseStatus === RESOLVED) { // 代码块三 条件一
      return new myPromise((resolve, reject) => {
        // 什么时候会出现resolve一进来就变成RESOLVED或者REJECTED? 同一个Promise实例被多次调用其then时,这样会走下面的逻辑,为确定then内部还是会异步执行,使用setTimeout
        setTimeout(() => {
          try {
            // 执行then内部的方法
            let x = onResolved(this.PromiseValue)
            resolvePromise(newPromise, x, resolve, reject);
          } catch (err) {
            reject(err)
          }
        })
      })
    }
    if (this.PromiseStatus === REJECTED) { // 代码块三 条件二
      return new myPromise((resolve, reject) => {
        setTimeout(() => {
          try {
            let x = onRejected(this.PromiseReason)
            resolvePromise(newPromise, x, resolve, reject);
          } catch (err) {
            reject(err)
          }
        })
      })
    }
    // 当没有触发resolve或者reject时,将onResolved和onRejected存放在onResolvedCallbacks/onRejectedCallbacks集合中
    if (this.PromiseStatus === PENDING) { // 代码块三 条件三
      return new myPromise ((resolve, reject) => {
        this.onResolvedCallbacks.push(value => {
          try {
            let x = onResolved(value)
            resolvePromise(newPromise, x, resolve, reject);
          } catch (err) {
            reject(err)
          }
        })
        this.onRejectedCallbacks.push(reason => {
          try {
            let x = onRejected(reason)
            resolve(x)
          } catch (err) {
            reject(err)
          }
        })
      })
    }
  }
}

根据以上代码,我们进行简单的分析:

new myPromise(function (resolve, reject) {
    console.log(1)
    resolve(2)
}).then(function (success) {
    console.log(success)
})

上面的代码中,首先构造函数初始化一些值

this.PromiseStatus = PENDING // 初始promise的状态
this.PromiseValue = null // 初始promise resolve状态时的value值
this.PromiseReason = null // 初始promise reject状态时的value值
this.onResolvedCallbacks = [] // 存储resolved状态对应的onResolvedCallbacks事件数组
this.onRejectedCallbacks = [] // 存储rejected状态对应的onRejectedCallbacks事件数组

例如上面简单的例子:其经历的步骤有:

  1. 首先进入myPromise构造函数内,进入代码块二,立即执行myPromise传进来的函数fn,这里PromiseStatus=PENDING

    try { // 代码块二
        // 立即执行new Promise内的
        fn(resolve, reject)
    } catch (err) {
        reject(err)
    }
    
  2. 执行fn时,进一步执行resolve函数,进入代码块一,这里首先对resolve传进来的参数进行了类型判断,然后我们可以看到setTimeout代码,这里将setTimeout内部的事件放在eventLoop的最后。这里PromiseStatus=PENDING

    setTimeout(() => {
        if (this.PromiseStatus == PENDING) {
          this.PromiseStatus = RESOLVED
          this.PromiseValue = value
          this.onResolvedCallbacks.map(cb => {
            this.PromiseValue = cb(this.PromiseValue)
          })
        }
      })
    
  3. 触发myPromise的then方法,进入到代码块三,进行条件判断,进入到代码块三的 条件三,返回一个新的myPromise实例,这里会立即执行下面代码

    this.onResolvedCallbacks.push(value => {
        try {
            let x = onResolved(value)
            resolvePromise(newPromise, x, resolve, reject);
        } catch (err) {
            reject(err)
        }
    })
    

    这个操作就是向onResolvedCallbacks里push一个函数

  4. 上述操作结束后,再执行前面放在eventLoop最后的事件

    setTimeout(() => {
        if (this.PromiseStatus == PENDING) {
          this.PromiseStatus = RESOLVED
          this.PromiseValue = value
          this.onResolvedCallbacks.map(cb => {
            this.PromiseValue = cb(this.PromiseValue)
          })
        }
    })
    

    这里找到更改了PromiseStatus的状态,并执行onResolvedCallbacks数组中的函数,这个函数是之前代码块三中的

    value => {
        try {
            let x = onResolved(value)
            resolvePromise(newPromise, x, resolve, reject);
        } catch (err) {
            reject(err)
        }
    }
    

所以,promise的then方法是如何确定在前面resolve或者reject执行完拿到结果再执行的,分两种情况:

a = new Promise((resolve, reject) => {
    resolve(1)
})
  • 对a只调用了一次then方法:这里进入then方法,判断状态为pending,直接返回一个new Promise(),像存放resolve状态的数组内push一个函数f。在所有同步操作完成后,在eventLoop的末尾执行resolve内部的函数,从resolve状态的数组中取出函数f并执行。
  • 对a被调用多次then方法:在第二次调用then方法时,前面的promise状态已经变为resolved或者reject,这样,进入then方法,判断状态为resolved或者reject,就setTimeout,将then内部的函数放在eventLoop的末尾。