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)
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容

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