美文网首页美文共赏
ES6 promise 用法小结

ES6 promise 用法小结

作者: 小王子__ | 来源:发表于2021-12-13 00:29 被阅读0次

    ES6 promise 用法小结

    Js 是一⻔单线程语言,早期解决异步问题,大部分是通过回调函数进行。

    比如我们发送 ajax 请求,就是常见的一个异步场景,发送请求后,一段时间服务器给我们响应,然后才拿到结果。如果我们希望在异步结束之后执行某个操作,就只能通过回调函数的方式进行操作

    const request = function (callback) {
      setTimeout(function () {
        callback()
      }, 1000)
    }
    request(function () {
      console.log(123)
    })
    // 以上代码执行结果:1s 后输出 123
    // request 就是一个异步函数,里面执行的 setTimeout 会在 1s 之后调用传入的 callback 函数, 
    // 如果后续还有内容需要在异步函数结束时输出,就需要多个异步函数进行嵌套,非常不利于后续的维护。
    
    setTimeout(function () {
      console.log(123)
      setTimeout(function () {
        console.log(321)
        // ...
      }, 2000)
    }, 1000)
    

    为了使回调函数以更优雅的方式进行调用,在 ES6 中引入了 promise,让异步 操作的变得「同步化」。

    1,Promise 基础

    通过 new Promise() 即可构造一个 promise 实例,这个构造函数接受一个函数,接受两个参数:resolvereject,代表改变实例的状态到 已完成 或是 已拒绝

    const promise = new Promise(function (resolve, reject) {
      console.log('promise called')
      setTimeout(function () {
        resolve()
      }, 3000)
    })
    
    promise.then(function () {
      console.log('promise resolve callback')
    })
    
    // 先打印出 promise called, 3s 后打印 promise resolve callback
    
    function promise1() {
      return new Promise(function (resolve, reject) {
        setTimeout(function () {
          console.log('1s后输出')
          resolve()
        }, 1000)
      })
    }
    
    function promise2() {
      return new Promise(function (resolve, reject) {
        setTimeout(function () {
          console.log('2s后输出')
          resolve()
        }, 2000)
      })
    }
    // 以下两个promise实例,串联起来即可写为:
    promise1().then(function () {
      return promise2()
    })
    或
    promise1().then(promise2)
    

    控制台打印结果:1s之后出现 1s后输出,再经过2s出现2s后输出。实例中,当前promise如果状态变为已完成(执行resolve方法),就会去执行 then 方法中的下一个 promise 函数。同样的如果promise变成已拒绝状态(执行reject方法),就会进入后续的异常处理函数中。

    function promise3() {
      return new Promise(function (resolve, reject) {
        var random = Math.random() * 10 // 随机一个 1 - 10的数字
        setTimeout(function () {
          if (random >= 5) {
            resolve(random) // 把随机生成的数字传给了 resolve, 在 then 中可以拿到这个值
          } else {
            reject(random)  // 把随机生成的数字传给了 reject,在 then 中可以拿到这个值
          }
        }, 1000)
      })
    }
    
    var onResolve = function (val) {  
      console.log('已完成:输出的数字是:', val)
    }
    
    var onReject = function (val) {
      console.log('已拒绝:输出的数字是:', val)
    }
    
    // promise 的then也可以接受两个参数,第一个参数是 resolve 后执行的,第二个参数是 reject 后执行的
    promise3().then(onResolve, onReject)
    
    // 也可以通过 .catch 方法拦截状态变为已拒绝时的 promise
    promise3().catch(onReject).then(onResolve)
    
    // 也可以通过 try catch 进行拦截状态变为已拒绝的 promise
    try {
      promise3().then(onResolve)
    } catch (e) {
      onReject(e)
    }
    

    以上使用3种方式拦截最终变为「已拒绝」状态的 promise,分别是使用 then 的第二个参数,使用 .catch 方法捕获前方 promise 抛出的异常,使用 try catch 拦截代码块中 promise 抛出的异常

    我们可以发现,在改变 promise 状态时调用 resolvereject 函数的时候,可以给下一步 then 中执行的函数传递参数。

    2,封装异步操作为promise

    我们可以将任何接受回调的函数封装为一个promise, 实例:

    // 原函数
    function func(callback) {
      setTimeout(function () {
        console.log('1s 后显示')
        callback()
      }, 1000)
    }
    
    var callback = function () {
      console.log('在异步结束后打印')
    }
    // 用传入回调函数的方式执行
    func(callback)
    
    image
    以上实例是最传统的使用传入回调函数的方式在异步结束后执行函数。我们可以通过封装 promise的方式,将这个异步函数变为 promise:
    function func() {
      return new Promise(function (resolve, reject) {
        setTimeout(function () {
          console.log('1s 后显示')
          resolve()
        })
      })
    }
    
    var callback = function () {
      console.log('在异步结束后打印')
    }
    
    func().then(function () {
      callback()
    })
    

    再比如,我们发送 ajax 请求也可以封装为 promise:

    function ajax(url, success, fail) {
      var client = new XMLHttpRequest();
      client.open('GET', url)
      client.onreadystatechange = function () {
        if (this.readyState !== 4) {
        // this.readyState扩展:
        // 0: 未初始化,还没调用 send() 方法
        // 1: 载入,已调用send()方法,正在发送请求
        // 2: 载入完成,send()执行完毕,已接受全部响应内容
        // 3: 交互,正在解析响应内容
        // 4: 完成,响应内容解析完成,可以直接使用responseText数据
          return
        }
        if (this.status === 200) {
          success(this.response)
        } else {
          fail(new Error(this.statusText))
        }
      }
      client.send()
    }
    
    ajax('http://localhost:8080/home/swiper', function (res) {
      console.log('成功')
      console.log(res)
    }, function (err) {
      console.log('失败', err)
    })
    
    image
    以上 ajax 请求,通过封装 promise 的方式,在原来的执行回调函数的地方,更改当前 promise的状态,就可以通过链式调用:
    function ajax(url) {
      return new Promise(function (resolve, reject) {
        var ct = new XMLHttpRequest();
        ct.open('GET', url)
        ct.onreadystatechange = function () {
          if (this.readyState !== 4) {
            return
          }
          if (this.status === 200) {
            resolve(this.response)
          } else {
            reject(new Error(this.statusText))
          }
        }
        ct.send()
      })
    }
    
    ajax('http://localhost:8080/home/swiper').catch(function () {
      console.log('失败')
    }).then(function (res) {
      console.log('成功')
      console.log(res)
    })
    

    我们可以把任何一个函数或者是异步函数改为promise,尤其是异步函数,改为 promise中后即可进行链式调用,增强可读性

    3,小总结

    • 1,promise 有三种状态,进行中(Pending)已完成(Fulfilled)已拒绝(Rejected),进行中状态可以更改为已完成 或 已拒绝,已经更改过状态后无法继续更改(例如从已完成改为已拒绝)。

    • 2,ES6 中的 Promise 构造函数,我们构造之后需要传入一个函数,他接受两个函数参数,执行第一个参数之后就会改变当前 promise 为已完成状态,执行第二个参数之后就会变为 已拒绝 状态。

    • 3,必须有一个then方法用以访问其当前值和原因。promise的 then 方法接受两个参数:promise.then(onFulfilled, onRejected) 他们都是可选参数,他们都是函数。如果 onFulfilledonRejected 不是函数,则需要忽略他们

    • 4,已拒绝的 promise,后续可以通过 .catch 方法或是 .then 方法的第二个参数或是 try catch 进行捕获。

    • 5,then方法可以被同一个promise调用多次。

      • 当 promise 成功执行的时候,所有的 onFulfilled 需按照其注册顺序依次回调
      • 当 promise 被拒绝执行的时候,所有的 onRejected 需按照其注册顺序依次回调
        then 方法必须返回一个 promise 对象: promise2 = promise1.then(onFulfilled, onRejected)
      • 只要 onFulfilled 或者 onRejected 返回一个值 xpromise2 都会进入 onFulfilled 状态
      • 如果 onFulfilled 或者 onRejected 抛出一个异常 e ,则 promise2 必须拒绝执行,并返回拒因 e
      • 如果 onFulfilled 不是函数且 promise1 状态变为已完成, promise2 必须成功执行并返回相同的值
      • 如果 onRejected 不是函数且 promise1 状态变为已拒绝, promise2 必须执行拒绝回调并返回相同的据因
    var promise1 = new Promise((resolve, reject) => {
      reject()
    })
    promise1
      .then(null, function () {
        return 123
      })
      .then(null, null)
      .then(null, null)
      .then(
        () => {
          console.log('promise2 已完成')
        },
        () => {
          console.log('promise2 已拒绝')
        })
    

    以上代码输出:promise2 已完成

    以上代码可改写为:

    var promise1 = new Promise(function (resolve, reject) { reject() })
    var promise2 = promise1.then(null, function () { return 123 })
    var promise3 = promise2.then(null, null) // 如果 onFulfilled 不是函数且 promise2 状态变为已完成, promise3 必须成功执行并返回和 promise2 相同的值, 即 123
    var promise4 = promise3.then(null, null) // 同理,promise4 也能拿到 123 的值
    promise4
      .then(val => {
        console.log('promise2 已完成', val)  // promise2 已完成 123
      }, () => {
        console.log('promise2 已拒绝')
      })
    

    实例:

    var promise1 = function () {
      return new Promise(function (resolve, reject) {
        setTimeout(function () {
          console.log(1)
          resolve()
        }, 1000)
      })
    }
    
    var promise2 = function () {
      return new Promise(function (resolve, reject) {
        setTimeout(function () {
          console.log(2)
          resolve()
        }, 2000)
      })
    }
    
    promise1()
      .then(function () {
        return promise2()  // 此处返回一个 promise 实例
      })
      .then(function () {
        console.log('已完成')
      }, function () {
        console.log('已拒绝')
      })
    
    image

    4, promise 构造函数上的 静态方法

    • 4.1, promise.resolve

    返回一个 promise 实例,并将它的状态设置为已完成,同时将他的结果作为传入 promise 实例的值

    var promise = Promise.resolve(123)
    promise.then(function (val) {
      console.log('已完成', val)
    })
    
    image

    Promise.resolve 的参数也可以处理对象、函数等内容

    • 4.2,promise.reject

    返回一个 promise 实例,并将它的状态设置为已拒绝,同时也将他的结果作为原因传入 onRejected 函数

    var promise = Promise.reject(123)
    promise.then(null, function (val) {
      console.log('已拒绝', val)
    })
    
    image
    • 4.3,Promise.all

    返回一个 promise 实例,接受一个数组,里面含有多个 promise 实例,当所有 promise 实例都成 已完成 状态时,进入已完成状态,否则进入已拒绝状态。

    var promise1 = function () {
      return new Promise(function (resolve, reject) {
        setTimeout(function () {
          console.log(1)
          resolve()
        }, 1000)
      })
    }
    
    var promise2 = function () {
      return new Promise(function (resolve, reject) {
        setTimeout(function () {
          console.log(2)
          resolve()
        }, 1000)
      })
    }
    
    Promise.all([promise1(), promise2()]).then(function () {
      console.log('全部 promise 均已完成')
    })
    
    image

    以上代码为多个 promise 同时进行,等待 1s 打印 1 之后,再等待 1s 就 会打印 2 和全部 promise 均已完成。

    • 4.4,Promise.race
      返回一个 promise 实例,接受一个数组,里面含有多个 promise 实例,当有一个 promise 实例状态改变时,就进入该状态且不可改变。这里所有的 promise 实例为竞争关系,只选择第一个进入改变状态的 promise 的值。
    var promise1 = function () {
      return new Promise(function (resolve, reject) {
        setTimeout(function () {
          console.log(1)
          resolve(1)
        }, 1000)
      })
    }
    
    var promise2 = function () {
      return new Promise(function (resolve, reject) {
        setTimeout(function () {
          console.log(2)
          resolve(2)
        }, 1000)
      })
    }
    
    Promise.race([promise1(), promise2()]).then(function (val) {
      console.log('有一个 promise 状态已经改变', val)
    })
    
    image

    5, generator / async await

    ES6 之后,我们可以使用 generator 和 async/await 来操作 promise,比起使用 promise 串行的调用来说,从语法层面 让调用关系 显得更加串行。

    function promise1() {
      return new Promise(function (resolve, reject) {
        setTimeout(function () {
          console.log(1)
          resolve()
        }, 1000)
      })
    }
    
    function promise2() {
      return new Promise(function (resolve, reject) {
        setTimeout(function () {
          console.log(2)
          resolve()
        }, 1000)
      })
    }
    
    // 使用 generator 函数
    function* gen() {
      yield promise1()
      yield promise2()
    }
    var g = gen()
    g.next()
    g.next() // 1 2
    
    // 使用 async/await 函数
    (async function () {
      try {
        await promise1()
        await promise2()
        console.log('已完成')
      } catch (e) {
        console.log(e)
        console.log('已拒绝')
      }
    }())  // 1 2 已完成
    

    相关文章

      网友评论

        本文标题:ES6 promise 用法小结

        本文链接:https://www.haomeiwen.com/subject/iaodfrtx.html