Learn Promise

Promise是抽象异步处理对象以及对其进行各种操作的组件。

API 三种类型:

  • Constructor
    从构造函数 Promise 来创建一个新建新 promise 对象作为接口。
    要想创建一个promise对象、可以使用 new 来调用 Promise 的构造器来进行实例化

    var promise = new Promise((resolve, reject) => {
    // 异步处理
    // 处理结束后、调用resolve 或 reject
    })
    
  • Instance Method
    对通过new生成的promise对象为了设置其值在 resolve(成功) / reject(失败)时调用的回调函数 可以使用 promise.then() 实例方法

    • promise.then(onFulfilled, onRejected)
    • resolve(成功)时, onFulfilled 会被调用
    • reject(失败)时, onRejected 会被调用
      只想对异常进行处理时,更好的选择是使用promise.catch(onRejected)
  • Static Method
    包括 Promise.all() 还有 Promise.resolve() 等在内,主要都是一些对Promise进行操作的辅助方法。

Promise workflow
示例代码:

function asyncFunction() {
  // new Promise 构造器之后,会返回一个promise对象
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('Async Hello world')
    }, 16)
  })
}

// asyncFunction这个函数会返回promise对象。对于这个promise对象,我们调用它的then方法来设置resolve后的回调函数, catch方法来设置发生错误时的回调函数。
asyncFunction()
  .then(value => {
    console.log(value) // => 'Async Hello world'(在这种情况下 catch 的回调函数并不会被执行(因为promise返回了resolve))
  })
  .catch(err => {
    console.log(err) // 如果运行环境没有提供 setTimeout 函数的话,那么上面代码在执行中就会产生异常,在catch 中设置的回调函数就会被执行。
  })

Promise的状态

用 new Promise 实例化的promise对象有以下三个状态:

  • "has-resolution" - Fulfilled
    resolve(成功)时。此时会调用 onFulfilled
  • "has-rejection" - Rejected
    reject(失败)时。此时会调用 onRejected
  • "unresolved" - Pending
    既不是resolve也不是reject的状态。也就是promise对象刚被创建后的初始化状态等

promise对象的状态,从Pending转换为Fulfilled或Rejected之后, 这个promise对象的状态就不会再发生任何变化。也就是说,Promise与Event等不同,在 .then 后执行的函数可以肯定地说只会被调用一次。

另外,Fulfilled和Rejected这两个中的任一状态都可以表示为Settled(不变的)。
Settled: resolve(成功) 或 reject(失败)。

创建promise对象的流程如下所示。

  1. new Promise(fn) 返回一个promise对象
  2. 在 fn 中指定异步等处理
    • 处理结果正常的话,调用 resolve(处理结果值)
    • 处理结果错误的话,调用 reject(Error对象)

实例:用Promise来通过异步处理方式来获取XMLHttpRequest(XHR)的数据。

function getURL(url) {
  return new Promise((resolve, reject) => {
    const req = new XMLHttpRequest()
    req.open('GET', url, true)
    req.onload = () => {
      if (req.status === 200) {
        resolve(req.responseText)
      } else {
        reject(new Error(req.statusText))
      }
    }
    req.onerror = () => {
      reject(new Error(req.statusText))
    }
    req.send()
  })
}

// 运行
const url = 'https://www.google.com.hk'
getURL(url)
  .then(onFulfilled = value => {
    console.log(value)
  })
  .catch(onRejected = error => {
    console.error(error)
  })

// 其实,.catch 只是 promise.then(undefined, onRejected) 的别名而已, 如下代码也可以完成同样的功能。
getURL(url)
  .then(onFulfilled, onRejected)

Promise.resolve

  • new Promise的快捷方式
    静态方法 Promise.resolve(value) 可以认为是 new Promise() 方法的快捷方式。
比如 Promise.resolve(42) 可以认为是以下代码的语法糖。

new Promise(resolve => {
  resolve(42)
})

// 方法 Promise.resolve(value) 的返回值也是一个Promise对象,所以我们可以像下面那样接着对其返回值进行 .then 调用。
Promise.resolve(42)
  .then(value => {
    console.log(value)
  })
  • 将thenable对象转换Promise对象
    这种机制要求thenable对象所拥有的 then 方法应该和Promise所拥有的 then 方法具有同样的功能和处理过程,在将thenable对象转换为promise对象的时候,还会巧妙的利用thenable对象原来具有的 then 方法。
    实例: jQuery.ajax(),它的返回值就是thenable的(这个对象具有 .then 方法)。

    $.ajax('/json/comment.json') // => 拥有 .then 方法的对象
    
    // 这个thenable的对象可以使用 Promise.resolve 来转换为一个promise对象
    const promise = Promise.resolve($.ajax('/json/comment.json')) // => promise对象
    promise.then(value => {
      console.log(value)
    })
    

Promise.reject

Promise.reject(error) 是和 Promise.resolve(value) 类似的静态方法,是 new Promise() 方法的快捷方式。

比如 Promise.reject(new Error('error')) 是以下代码的语法糖。

new Promise((resolve, reject) => {
  reject(new Error('error'))
})

// 这段代码的功能是调用该Promise对象通过then指定的 onRejected 函数,并将错误(Error)对象传递给这个 onRejected 函数。
Promise.reject(new Error('BOOM!'))
  .catch(err => {
    console.error(err)
  })

避免对异步回调函数进行同步调用

实例:根据执行时DOM是否已经装载完毕来决定是对回调函数进行同步调用还是异步调用。

function onReady(fn) {
  const readyState = document.readyState
  if (readyState === 'interactive' || readyState === 'complete') {
    setTimeout(fn, 0)
  } else {
    window.addEventListener('DOMContentLoaded', fn)
  }
}

onReady(() => {
  console.log('DOM fully loaded and parsed')
})
console.log('==Starting==')

// 用Promise重写
function onReadyPromise() {
  return new Promise((resolve, reject) => {
    const readyState = document.readyState
    if (readyState === 'interactive' || readyState === 'complete') {
      resolve()
    } else {
      window.addEventListener('DOMContentLoaded', resolve)
    }
  })
}

onReadyPromise()
  .then(() => {
    console.log('DOM fully loaded and parsed')
  })
console.log('==Starting==')

Promise chain

  • Promise#then
  • Promise#catch

promise chain 中如何传递参数
实例:如果 Task A 想给 Task B 传递一个参数,那就在 Task A 中 return 的返回值,会在 Task B 执行时传给它。

function doubleUp(value) {
  return value * 2
}
function increment(value) {
  return value + 1
}
function output(value) {
  console.log(value) // => (1 + 1) * 2
}

const promise = Promise.resolve(1)
promise
  .then(increment) 
  .then(doubleUp)  
  .then(output) 
  .catch(err => {
    // promise chain中出现异常的时候会被调用
    console.error(err)
  })

return的值会由 Promise.resolve(return的返回值) 进行相应的包装处理,因此不管回调函数中会返回一个什么样的值,最终 then 的结果都是返回一个新创建的Promise对象。

使用Promise同时处理多个异步请求

在多个promise对象都变为FulFilled状态的时候才要进行某种处理时:

  • Promise#then

    function getURL(URL) {
      return new Promise((resolve, reject) => {
        const req = new XMLHttpRequest()
        req.open('GET', URL, true)
        req.onload = () => {
          if (req.status === 200) {
            resolve(req.responseText)
          } else {
            reject(new Error(req.statusText))
          }
        }
        req.onerror = () => {
          reject(new Error(req.statusText))
        }
        req.send()
      })
    }
    
    const request = {
      comment: function getComment() {
        return getURL('http://azu.github.io/promises-book/json/comment.json')
                .then(JSON.parse)
      },
      people: function getPeople() {
        return getURL('http://azu.github.io/promises-book/json/people.json')
                .then(JSON.parse)
      }
    }
    
    function main() {
      function recordValue(results, value) {
        results.push(value)
        return results
      }
      // [] 用来保存初始化的值
      const pushValue = recordValue.bind(null, [])
      return request.comment()
                    .then(pushValue)
                    .then(request.people)
                    .then(pushValue)
    }
    // 运行的例子
    main()
      .then(value => {
        console.log(value)
      })
      .catch(err => {
        console.error(err)
      })
    

    为了应对这种需要对多个异步调用进行统一处理的场景,Promise准备了 Promise.all 和 Promise.race 这两个静态方法。

  • Promise.all
    Promise.all 接收一个 Promise对象的数组作为参数,当这个数组里的所有Promise对象全部变为resolve或reject状态的时候,它才会去调用 .then 方法。
    之前例子中的 getURL 返回了一个promise对象,它封装了XHR通信的实现。 向 Promise.all 传递一个由封装了XHR通信的promise对象数组的话,则只有在全部的XHR通信完成之后(变为FulFilled或Rejected状态)之后,才会调用 .then 方法。
    上面的例子用Promise.all改写(除main()以外其他部分不变)如下:

    function main() {
      return Promise.all([request.comment(), request.people()])
    }
    

    与之前的例子相比:

    • main中的处理流程显得非常清晰
    • Promise.all 接收 promise对象组成的数组作为参数
      在上面的代码中,request.comment() 和 request.people() 会同时开始执行,而且每个promise的结果(resolve或reject时传递的参数值),和传递给Promise.all 的promise数组的顺序是一致的。
      也就是说,这时候 .then 得到的promise数组的执行结果的顺序是固定的,即
      [comment, people]。
main()
  .then(results => {
    console.log(results) // 按照[comment, people]的顺序
  })

传递给 Promise.all 的promise并不是一个个的顺序执行的,而是同时开始、并行执行的。

  • Promise.race
    Promise.all 在接收到的所有的对象promise都变为 FulFilled 或者 Rejected 状态之后才会继续进行后面的处理,与之相对的是 Promise.race 只要有一个promise对象进入FulFilled 或者 Rejected 状态的话,就会继续进行后面的处理。

    const winnerPromise = new Promise(resolve => {
      setTimeout(() => {
        console.log('this is winner')
        resolve('this is winner --resolve')
      }, 0)
    })
    const loserPromise = new Promise(resolve => {
      setTimeout(() => {
        console.log('this is loser')
        resolve('this is loser --resolve')
      }, 0)
    })
    
    // 第一个promise变为resolve(FulFilled)后程序停止
    Promise.race([winnerPromise, loserPromise])
      .then(function (value) {
        console.log(value) // => 'this is winner'
      })
    
    // 输出结果
    this is winner
    this is loser
    this is winner --resolve
    

    winnter和loser promise对象的 setTimeout 方法都会执行完毕,console.log 也会分别输出它们的信息。
    也就是说,Promise.race 在第一个promise对象变为Fulfilled之后,并不会取消其他promise对象的执行。

  • 总结

    • 使用 promise.then(onFulfilled, onRejected)
      在 onFulfilled 中发生异常的话,在 onRejected 中是捕获不到这个异常的。
    • 在 promise.then(onFulfilled).catch(onRejected) 的情况下
      .then 中产生的异常能在 .catch 中捕获
    • .then 和 .catch 在本质上是没有区别的
      需要分场合使用。.then(null, onRejected) 等同于.catch(onRejected)
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • Promise 对象 Promise 的含义 Promise 是异步编程的一种解决方案,比传统的解决方案——回调函...
    neromous阅读 12,774评论 1 56
  • 本文适用的读者 本文写给有一定Promise使用经验的人,如果你还没有使用过Promise,这篇文章可能不适合你,...
    HZ充电大喵阅读 12,040评论 6 19
  • 1.promise简介 1.1 Promise本意是承诺,在程序中的意思就是承诺我过一段时间后会给你一个结果...
    常青_1f93阅读 4,288评论 0 1
  • 前言 本文旨在简单讲解一下javascript中的Promise对象的概念,特性与简单的使用方法。并在文末会附上一...
    _暮雨清秋_阅读 6,616评论 0 3
  • 特别说明,为便于查阅,文章转自https://github.com/getify/You-Dont-Know-JS...
    杀破狼real阅读 4,373评论 0 2