一文彻底搞懂:js-Promise

398 阅读7分钟

Promise简介

开始讲解Promise之前,我们先来一个需求,假设不使用Promise,向服务端发送三个请求A,B,C,先发送A,成功之后发送B,成功之后发送C。我们使用setTimeout 来模拟网络请求,代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Promise</title>
</head>
<body>
    <script type="text/javascript">
        function request(successCallback) {
            //使用setTimeout模拟网络请求
            setTimeout(function(){
                successCallback('获取数据成功');
            }, 1000);
        }

        //A开始请求
        request((result)=>{
            //A请求成功
            console.log(result + '---A');
            //B开始请求
            request((result)=>{
                //B请求成功
                console.log(result + '---B');
                //C开始请求
                request((result)=>{
                    //C请求成功
                    console.log(result + '---C');
                })  
            })
        })
    </script>
</body>
</html>

大家发现别扭的地方了没?每个请求都需要在上一个请求的callback里触发,造成代码层层嵌套!。 多层的嵌套会导致我们的代码晦涩难懂。我们使用Promise语法进行改造:

function request() {
  return new Promise((resolve, reject)=>{
    setTimeout(()=>{
      resolve('获取数据成功');
    }, 1000)
  });
}

request().then((result)=>{
  console.log(result + '-----A')
  return request()
}).then((result)=>{
  console.log(result + '-----B')
  return request()
}).then((result)=>{
  console.log(result + '-----C')
  return request()
})

我们可以发现,通过Promise 实现了以同步的流程表示异步的操作,成功解决了代码嵌套的问题,代码结构清晰。 通过then 方法实现异步代码成功回调。接下来,我们将详细介绍Promise 的使用。

Promise的使用

创建Promise对象

在 js 中,Promise是一个类,创建 Promise 对象直接使用 new 关键字即可。 Promise 构造函数接收一个方法作为参数,这个方法有两个参数: resolve 和 reject。

promise对象创建成功之后会 立即执行 其方法参数。下面代码是我们创建了一个 Promise 对象,它会立即执行,输出结果是 1 2 3

console.log('1');
const promise = new Promise(function(resolve, reject){ 
  console.log('2');
})
console.log('3')

Promise 的状态

Promise 有三种状态。

  • Pending 待定状态,即初始状态,既没有兑现,也没有被拒绝。
  • fulfilled (resolve) 已兑现状态。
  • rejected 已拒绝状态。
状态间的转换

Pending 状态可以切换到 fulfilled 状态, 通过调用 resolve 方法。

Pending 状态可以切换到 rejected 状态, 通过调用 reject 方法。

一旦Promise切换到 fulfilled 状态或者 rejected 状态 就不能切换到其他状态了。

当调用resolve方法,将promise的状态切换到fulfilled的时候,会调用 promise 的 then 方法。如果promise被多个 then 方法订阅,那么所有的 then 方法都会执行。

const promise = new Promise(function(resolve, reject){
  resolve()
})

promise.then(function(){
  console.log('执行成功1')
})
promise.then(function(){
  console.log('执行成功2')
})

当调用reject方法,将promise的状态切换到rejected的时候,会调用 promise 的 catch 方法。如果promise被多个 catch 方法订阅,那么所有的 catch 方法都会执行。

const promise = new Promise(function(resolve, reject){
	reject()
})
promise.catch(function(){
  console.log('执行失败1')
})
promise.catch(function(){
  console.log('执行失败2')
})

值得注意的是,then 方法可以接收两个方法参数,第一个方法参数会在 promise 切换到 fulfilled 状态的时候被调用,第二个方法参数 会在promise切换到rejected状态的时候调用。

  • catch 其实是 then(undefined, () => {}) 的语法糖
  • 如果需要分开监听, 也就是通过then监听成功通过catch监听失败,那么必须使用链式编程, 否则会报错
const promise = new Promise(function(resolve, reject){
  // resolve()  //触发成功回调
  reject() // 触发失败回调
})
promise.then(function(){
  console.log('执行成功')
}, function(){
  console.log('执行失败')
})

Promise 使用注意点

  • 可以通过 resolve 和 reject 分别向 then 和 catch 方法传递参数。
const promise = new Promise(function(resolve, reject){
  resolve('123')   //执行成功---123
  // reject('456')  //执行失败---456
})
promise.then(function(param){
  console.log('执行成功---' + param)
}).catch(function(param){
  console.log('执行失败---' + param)
})

//下面这种写法和上面一样
const promise2 = new Promise(function(resolve, reject){
  resolve('123')
  // reject('456')
})
function success(param) {
  console.log('执行成功---' + param)
}
function failture(param) {
  console.log('执行失败---' + param)
}
// 这两种调用一样,同样有效。
// promise2.then(success, failture)
promise2.then(success).catch(failture)
  • 一次状态切换可以触发多个then 和 catch
const promise = new Promise(function(resolve, reject){
  resolve()  //执行成功1 执行成功2
  // reject()  //执行失败1 执行失败2
})

promise.then(function(){
  console.log('执行成功1')
}).catch(function(){
  console.log('执行失败1')
})
promise.then(function(){
  console.log('执行成功2')
}).catch(function(){
  console.log('执行失败2')
})
  • then 方法和 catch 方法每次执行之后都会返回一个新的promise对象
const promise = new Promise(function(resolve, reject){
  resolve()
  // reject()
})
const promise2 = promise.then(function(){
  console.log('执行成功')
}).catch(function(){
  console.log('执行失败')
})
console.log(promise2)
console.log(promise == promise2)

从上面的代码中我们可以看出,then 方法或者catch 执行后,返回了一个新的Promise对象,且与上一个promise对象不同。

  • 在 then 方法或者 catch 方法中返回值,会传递给新promise对象的then方法。需要注意的是,无论是 resolve 还是 reject 触发的状态改变,都会传递给下一个promise 的 then 方法。

    如下代码:执行失败2 是触发不了的。

const promise = new Promise(function(resolve, reject){
  // resolve()
  reject()
})
const promise2 = promise.then(function(){
  console.log('执行成功1')
  return '2222'
}).catch(function(){
  console.log('执行失败')
  return '3333'
})

promise2.then(function(param){
  console.log('执行成功2----' + param)
}).catch(function(param){
  console.log('执行失败2----' + param)
})
  • 如果then方法返回的是一个Promise对象, 那么会将返回的Promise对象的执行结果中的值传递给下一个then方法。
    • 如果promise的状态是失败, 但是没有对应失败的监听就会报错
    • then方法会返回一个新的promise, 新的promise会继承原有promise的状态
    • 如果新的promise状态是失败, 但是没有对应失败的监听也会报错。
const promise = new Promise(function(resolve, reject){
  resolve('111')
})

const promise2 = new Promise(function(resolve, reject){
  reject('222')
})

const promise3 = promise.then(function(param){
  console.log('执行成功1---' + param)
  return promise2
}).catch(function(param){
  console.log('执行失败1---' + param)
})

执行结果是:

执行成功1---111

执行失败1---222

const promise = new Promise(function(resolve, reject){
  resolve('111')
})

const promise2 = new Promise(function(resolve, reject){
  resolve('222')
})

const promise3= promise.then(function(param){
  console.log('执行成功1---' + param) // 执行成功1---111
  return promise2
}).catch(function(param){
  console.log('执行失败1---' + param)
  return promise2
})

promise3.then(function(param){
  console.log('执行成功3---' + param) // 执行成功3---222
}).catch(function(param){
  console.log('执行失败3---' + param)
})

执行结果是:

执行成功1---111

执行成功3---222

  • 如果promise的状态是失败, 但是没有对应失败的监听就会报错
const promise = new Promise(function(resolve, reject){
  reject()
})

Uncaught (in promise) undefined

  • 和then方法第二个参数的区别在于, catch方法可以捕获上一个promise对象then方法中的异常
promise.then(function () {
  console.log("成功");
  abc
}).catch(function (e) {
  console.log("失败", e);
});

Promise race

promise 的 race 方法是一个静态方法,它返回一个promise对象,接收一个 promise数组作为参数,当数组内,谁的状态先发生改变,谁先触发race对象的then 或者 catch。

let p1 = new Promise(function(resolve, reject) {
  setTimeout(() => {
    resolve('1') 
  }, 2000);
})
let p2 = new Promise(function(resolve, reject) {
  setTimeout(() => {
    reject('2')
  }, 1000);
})

Promise.race([p1, p2]).then(function(value){
  console.log(value)
}).catch(function(e){
  console.log('失败-'+ e)
})

Promise all

promise 的 all 方法是一个静态方法,它返回一个promise对象,接收一个 promise数组作为参数,当数组内,所有的promise 对象的状态都变为成功,就会调用then方法,返回所有promise对象执行成功的集合。 当有一个promise执行失败,就会调用 catch 并会停止其他promise的调用。

下面这个例子,最终执行结果是 [1, 2, 3]

let p1 = new Promise(function(resolve, reject) {
  resolve('1')
})
let p2 = new Promise(function(resolve, reject) {
  resolve('2')
})
let p3 = new Promise(function(resolve, reject) {
  resolve('3')
})

Promise.all([p1, p2, p3]).then(function(value){
  console.log(value) // [1, 2, 3]
}).catch(function(e) {
  console.log(e) 
})

而下面这个例子,触发catch的调用,输出 2

let p1 = new Promise(function(resolve, reject) {
  resolve('1')
})
let p2 = new Promise(function(resolve, reject) {
  reject('2')
})
let p3 = new Promise(function(resolve, reject) {
  resolve('3')
})

Promise.all([p1, p2, p3]).then(function(value){
  console.log(value) // [1, 2, 3]
}).catch(function(e) {
  console.log(e) 
})

Promise实现

<script type="text/javascript">
// 定义状态
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

class MyPromise {
  constructor(handle){
    //0. 初始化默认状态
    this.status = PENDING

    // 保留参数
    this.value = undefined
    this.reason = undefined

    // 保留多个 then cath
    this.onResolveCallBacks = []
    this.onRejectCallBacks = []

    //判断是否传入了一个函数
    if(!this._isFunction(handle)) {
      throw new Error('请传入一个函数')
    }

    //2. 调用传入的参数, 绑定 _resolve 与 _reject
    handle(this._resolve.bind(this), this._reject.bind(this))
  }

  _resolve(value){
    if(this.status === PENDING) {
      this.status = FULFILLED
      this.value = value
      this.onResolveCallBacks.forEach((onResolveCallBack)=>{
        onResolveCallBack(this.value)
      })
    }

  }
  _reject(reason){
    if(this.status === PENDING) {
      this.status = REJECTED
      this.reason = reason
      this.onRejectCallBacks.forEach((onRejectCallBack)=>{
        onRejectCallBack(this.reason)
      })
    }

  }

  _isFunction(fun) {
    return typeof fun === 'function'
  }

  catch(onRejected) {
    this.then(undefined, onRejected)
  }

  then(onResolved, onRejected) {
    //返回新的Promise
    return new MyPromise((nextResolve, nextReject)=>{
      //判断当前promise状态,如果是成功
      if (this._isFunction(onResolved)) {
        if (this.status === FULFILLED) {
          try {
            let result = onResolved(this.value)
            if (result instanceof MyPromise) {
              result.then(nextResolve, nextReject)
            } else {
              nextResolve(result)
            }
          }catch(e){
            nextReject(e)
          }

        }
      }

      // if (this._isFunction(onRejected)) {
      //判断当前promise状态,如果是失败
      if (this.status === REJECTED) {
        try {
          let result = onRejected(this.reason)
          if (result instanceof MyPromise) {
            result.then(nextResolve, nextReject)
          } else if (result !== undefined) {
            //返回值不是promise,则将返回值传递给下一个promise的then
            nextResolve(result)
          } else {
            //如果没有返回值,下一个promise的状态跟随当前promise
            nextReject()
          }
        }catch(e){
          nextReject(e)
        }
      }
      // }

      // 如果当前状态是PENDING
      if(this.status === PENDING) {
        if (this._isFunction(onResolved)) {
          //保留参数,等到状态改变的时候触发调用
          this.onResolveCallBacks.push(()=>{
            try {
              let result = onResolved(this.value)
              if (result instanceof MyPromise) {
                result.then(nextResolve, nextReject)
              } else {
                nextResolve(result)
              }
            }catch(e){
              nextReject(e)
            }
          })
        }
        // if (this._isFunction(onRejected)) {
        //保留参数,等到状态改变的时候触发调用
        this.onRejectCallBacks.push(()=>{
          try {
            let result = onRejected(this.reason)
            if (result instanceof MyPromise) {
              result.then(nextResolve, nextReject)
            } else if (result !== undefined) {
              nextResolve(result)
            } else {
              nextReject()
            }
          } catch(e) {
            nextReject(e)
          }

        })
        // }  
      }
    })
  }
}
</script>

总结

  1. Promise特点
    1. 创建时必须传入一个函数, 否则会报错
    2. 会给传入的函数设置两个回调函数
    3. 刚创建的Promise对象状态是pending
    4. 状态一旦发生改变就不可再次改变
    5. 可以通过then来监听状态的改变
      1. 如果添加监听时状态已经改变, 立即执行监听的回调
      2. 如果添加监听时状态还未改变, 那么状态改变时候再执行监听回到
      3. 同一个Promise对象可以添加多个then监听, 状态改变时所有的监听按照添加顺序执行
    6. then方法每次执行完毕都会返回一个新的Promise对象
    7. 上一个Promise对象的then可以给下一个Promise的then传递数据
      1. 无论上一个是在成功的回调还是失败的回调传递的参数都会传递给下一个成功的回调
      2. 如果上一个传递的是Promise对象, 那么传给下一个的成功还是失败由传递的Promise状态决定
    8. then方法返回的Promise对象的状态和前一个Promise的状态默认相同
    9. 后一个Promise对象的then可以捕获前一个Promise then的异常
    10. catch方法就是then方法的语法糖 then(undefined, function(){});

文章涉及的代码可在公众号中回复 promise 获取。

参考资料

developer.mozilla.org/zh-CN/docs/…

点赞,评论是对我最大的鼓励。