Promise 的含义

异步编程的解决方案,比传统的回调函数和事件更合理和强大。
Promise 最早由社区提出、实现,ES6 将其写入了语言标准,统一了用法,原生提供了 Promise 对象。
Promise 简单说是一个容器,里面保存着未来才会结束的事件(通常是异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。
Promise 提供统一的 API,各种异步操作都可以用同样的方法来处理。

Promise 的两个特点

  1. 对象的状态不受外界影响。Promise 对象代表异步操作,有三种状态,pending(进行中)、fulfilled(已成功)、reject(已失败)。只有异步操作的结果
    可以决定当前是哪一种状态,任何其他操作都无法改变这个状态,这也是 Promise 这个名字的由来,它的英语意思是“承诺”,表示其他手段无法改变。
  2. 一旦状态改变,就不会再变。pending->fulfilled 或者 pending->reject,状态的转变只有这两种可能。如果改变已经发生了,你再对 Promise 对象添加回调函数,
    也会立即得到这个结果。

有了 Promise 对象,就可以将异步操作以同步操作的流程表达出来,避免了层层函数嵌套的回调函数。

Promise 也有缺点。

  1. 无法取消 Promise,一旦新建它就会立即执行,无法中途取消。
  2. 如果不设置回调函数,Promise 内部抛出错误,不会反映到外部。
  3. pending 状态时,无法得知当前进展到哪一段(刚刚开始还是即将完成)。
    1. 一般来说某些事件不断反复发生,使用Stream模式比Promise更好。

基本用法

  1. const promise = new Promise(function(resolve, reject) {
  2. if (false) {
  3. setTimeout(function() {
  4. resolve(666)
  5. }, 500)
  6. } else {
  7. setTimeout(function() {
  8. reject(555)
  9. }, 2000)
  10. }
  11. })
  12. promise.then(
  13. function(data) {
  14. console.log(666)
  15. },
  16. function(error) {
  17. console.log(error)
  18. },
  19. )

使用 Promise 的一个例子,主要是觉得 setTimeout 的写法比较有新意。

  1. function timeout(ms) {
  2. return new Promise((resolve, reject) => {
  3. setTimeout(resolve, ms, 'done')
  4. })
  5. }
  6. timeout(2000).then(data => {
  7. console.log(data)
  8. })

记住 Promise 新建后就会执行

  1. const aPromise = new Promise((resolve, reject) => {
  2. console.log('Promise是立即执行,不是调用才执行,知道不!')
  3. resolve(666)
  4. console.log('其实,你虽然 resolve 了,但是我还是会执行!')
  5. return
  6. console.log('这里就不会执行了,因为前面已经return了')
  7. })
  8. aPromise
  9. .then(data => {
  10. console.log('Promise执行结束了!', data)
  11. })
  12. .catch(e => {
  13. console.log(e)
  14. })
  15. console.log('我就是一个普通的输出!')

使用 Promise 来做异步加载图片

  1. const loadingImageAsync = url => {
  2. return new Promise((resolve, reject) => {
  3. const image = new Image()
  4. image.onload = () => {
  5. resolve(image)
  6. }
  7. image.onerror = error => {
  8. reject(new Error('图片打不开,小主!' + url))
  9. }
  10. image.src = url
  11. })
  12. }
  13. loadingImageAsync('http://frontend.llccing.cn/poweredby.png')
  14. .then(data => {
  15. console.log(data)
  16. })
  17. .catch(error => {
  18. console.log(error)
  19. })
  20. // 第二个例子,errr是一个不存在的图片,会触发reject,同时被catch捕获。
  21. loadingImageAsync('http://frontend.llccing.cn/errr.png')
  22. .then(data => {
  23. console.log(data)
  24. })
  25. .catch(error => {
  26. console.log(error)
  27. })

使用Promise对象实现Ajax操作

  1. const getJSON = (url) => {
  2. const promise = new Promise((resolve, reject) => {
  3. const handler = function () {
  4. if (this.readyState !== 4) {
  5. return;
  6. }
  7. if (this.status === 200) {
  8. resolve(this.response)
  9. } else {
  10. reject(new Error(this.satusText))
  11. }
  12. }
  13. const client = new XMLHttpRequest();
  14. client.open('get', url);
  15. client.onreadystatechange = handler;
  16. client.responseType = 'json';
  17. client.setRequestHeader('Accept', 'application/json');
  18. client.send();
  19. })
  20. return promise
  21. }
  22. // 因为实际不存在这个url,所以代码会执行到catch
  23. getJSON('/api/name/zhangsan').then(resp => {
  24. console.log(resp)
  25. }).catch(error => {
  26. console.log(error)
  27. })

如下p1和p2都是Promise实例,p2的resolve方法将p1作为参数,即一个异步操作的结果返回另一个异步操作。

那么这是p1的状态就会传给p2,就是说,p1的状态决定了p2的状态。如果p1是pending,那么p2的回调函数就会等待p1的状态变化;如果p1的状态是resolved或者rejected,那么p2的回调函数就会立刻执行。

  1. const p1 = new Promise((resolve, reject) => {
  2. setTimeout(() => {
  3. reject(new Error('fail'))
  4. }, 3000)
  5. })
  6. const p2 = new Promise((resolve, reject) => {
  7. setTimeout(() => {
  8. resolve(p1)
  9. }, 1000);
  10. })
  11. p2.then(result => {
  12. console.log(result)
  13. }).catch(error => {
  14. console.log(error)
  15. })

再次注意,调用resolve或者reject并不会终结Promise的函数执行。

一般来说,resolve或者reject后,Promise的是使命就完成了,所以最好在resolve或者reject前面加上return,如下面的第二个例子。

  1. new Promise((resolve, reject) => {
  2. resolve(1)
  3. // 这里的2还是会输出的
  4. console.log(2)
  5. }).then(res => {
  6. console.log(res)
  7. })
  8. // 这样直接return
  9. new Promise((resolve, reject) => {
  10. return resolve(1)
  11. console.log(2)
  12. }).then(res => {
  13. console.log(res)
  14. })

Promise.prototype.then()

Promise实例具有then方法,也就是说then方法是定义在原型对象Promise.prototype上的。then方法的第一个参数resolved状态的回调函数,第二个参数(可选)是rejected状态的回调函数。

then方法返回的是新的Promise实例,所以可以采用链式写法,即then方法后面再调用一个then方法。

  1. getJSON('./data.json').then(function (json) {
  2. console.log(json)
  3. return json.data
  4. }).then(function (data) {
  5. console.log(data)
  6. })

采用链式的then,可以指定一组按照顺序调用的回调函数。这时前一个回调函数有可能返回的还是一个Promise对象(即有异步操作),这时后一个回调函数就会等待该Promise对象的状态发生变化,才会被调用。

  1. getJSON('./data.json')
  2. .then(function(json) {
  3. console.log(json.url)
  4. return getJSON(json.url)
  5. })
  6. .then(
  7. function funcA(data) {
  8. console.log('resolved', data)
  9. },
  10. function funcB(err) {
  11. console.log('rejected', err)
  12. },
  13. )

改为箭头函数,则更为简洁

  1. getJSON('./data.json')
  2. .then(function(json) {
  3. console.log(json.url)
  4. return getJSON(json.url)
  5. })
  6. .then(
  7. function funcA(data) {
  8. console.log('resolved', data)
  9. },
  10. function funcB(err) {
  11. console.log('rejected', err)
  12. },
  13. )

Promise.prototype.catch()

Promise.prototype.catch方法是.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数。

  1. promise.then(data => console.log(data)).catch(err => console.log(55, err))
  2. promise
  3. .then(data => console.log(data))
  4. .then(null, error => {
  5. console.log(66, error)
  6. })
  7. promise
  8. .then(data => console.log(data))
  9. .then(undefined, error => {
  10. console.log(77, error)
  11. })

Promise抛出一个错误,就会被catch方法指定的回调函数捕获。下面的两种写法是等价的

  1. const promise = new Promise((resolve, reject) => {
  2. try {
  3. throw new Error('reject test')
  4. } catch (e) {
  5. reject(e)
  6. }
  7. })
  8. promise.catch(error => console.log(1, error))
  9. const promise2 = new Promise((resolve, reject) => {
  10. reject(new Error('reject test2'))
  11. })
  12. promise2.catch(error => console.log(2, error))

比较上面两种写法,可以发现reject方法的作用,等同于抛出错误。

如果Promise状态已经是resolved,再抛出错误时无效的。

  1. const promise = new Promise((resolve, reject) => {
  2. resolve(666)
  3. throw new Error('error test')
  4. })
  5. promise.then(data=>console.log(1, data)).catch(error=> console.log(2, error))

Promise对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获。
一般来说建议在then方法中定义rejected状态的回调函数,总是使用catch方法。

跟传统的try/catch不同的是,Promise对象如果没有指定catch方法的回调函数进行错误处理,那么抛出的错误不会传递到外层代码,即没有任何反应。

  1. const someAsyncThing = function () {
  2. return new Promise((resolve, reject) => {
  3. // 此处会报错,因为x没有声明
  4. resolve(x+2)
  5. })
  6. }
  7. someAsyncThing().then(() => {
  8. console.log('everything is fine! ')
  9. })
  10. setTimeout(() => {
  11. console.log(666)
  12. }, 2000);

上述代码,setTimeout函数间隔2s后还是会执行,证明Promise会吃掉错误。

node中有一个unhandleRejection事件,专门监听未捕获的rejected事件,

  1. process.on('unhandledRejection', (error, p) => {
  2. console.log('wow', error)
  3. })

如果catch处理中还是有报错,那么可以再进行catch处理

  1. const promise = new Promise((resolve, reject) => {
  2. resolve(x + 2)
  3. })
  4. promise
  5. .then(data => {
  6. console.log(data)
  7. })
  8. .catch(error => {
  9. console.log('捕获, ', error)
  10. console.log('捕获, ', y + 2)
  11. })
  12. .catch(error => {
  13. console.log('再捕获, ', error)
  14. })

Promise.prototype.finally()

finally方法用于指定不管Promise对象最后状态如何,都会执行的操作。该方法是ES2018引入的。

finally方法不接受任何参数。所以不知道Promise的状态是fulfilled还是rejected。这表明finally里的操作应该是状态无关的,不依赖Promise的执行结果。

  1. const promise = new Promise((resolve, reject) => {
  2. resolve(666)
  3. })
  4. // 注意,finally 在node环境中,提示没有该方法,可能与node版本有关系,但是Chrome浏览器下正常。
  5. promise
  6. .then(data => console.log(data))
  7. .finally(() => {
  8. console.log('always do things')
  9. })

finally本质是then的特例

Promise.all()

Promise.all()方法用于将多个Promise实例,包装成一个新的Promise实例。

  1. const p1 = new Promise((resolve, reject) => {
  2. resolve(1)
  3. })
  4. const p2 = new Promise((resolve, reject) => {
  5. setTimeout(() => {
  6. resolve(2)
  7. }, 2000)
  8. })
  9. const p3 = new Promise((resolve, reject) => {
  10. resolve(3)
  11. })
  12. const p = Promise.all([p1, p2, p3]).then(data => {
  13. console.log(data)
  14. })

p的状态有p1、p2、p3决定:

  1. p123都是fulfilled,p是fulfilled。此时p123的返回值组成数组,传递给p的回调函数。
  2. p123有一个是rejected状态,p就是rejected。支持第一个被rejected的实例返回值,会传递给p的回调函数。

注意,要是作为Promise.all的参数的Promise自己定义了catch函数,那么Promise.all的catch不会再次捕获,而会执行 Promise.all.then方法

  1. const p1 = new Promise((resolve, reject) => {
  2. resolve(1)
  3. })
  4. const p2 = new Promise((resolve, reject) => {
  5. reject(new Error('test error'))
  6. }).catch(error => error)
  7. const p = Promise.all([p1, p2])
  8. .then(data => console.log(data))
  9. .catch(error => {
  10. console.log(2, error)
  11. })

如果p2不定义catch,那么则会执行 Promise.all的catch方法。

Promise.race()