Promise 是异步编程的一种解决方案。简单来说 Promise 就是一个容器,里面保存着某个未来才会结束的异步操作的结果。
从语法上说,Promise 是一个对象,通过它可以获取异步操作的消息。
在谈论 Promise 之前我们先看一段代码:
在日常开发中,我们经常会处理一些异步操作,例如:发送 Ajax 请求,setTimeout 等。处理这些异步操作我们一般会传递一个回调函数,然而当我们要进行一连串的异步顺序操作的时候,回调代码就会自动往右边进行偏离,就像金字塔那样。相信你已经遇到过,这就是我们常常说的“金字塔回调”。
首先,从视觉看,金字塔回调会有多层的嵌套回调函数,结尾会有大量的花括号和圆括号,例如上述代码。并且,这样写出来的代码不优雅,阅读起来也比较费力,我们也无法在内部使用 throw new Error() 并在外部进行捕获异常。
Promise 的出现就是为了主要解决这两个主要问题:它可以让我们以同步的方式编写异步代码,同时我们也可以优雅的捕获错误和异常。
Promise 最初起源于社区,现在已经被写入了 ES6 的规范中,并且主流浏览器都已经开始支持了,所以本文就介绍在代码中如何使用 Promise。
1、创建Promise实例
Promise 是一个构造函数,使用时我们需要先使用 new 创建一个 Promise 实例。
在上面代码中我们可以看到:
1)构造函数接受一个函数作为参数,该函数有两个参数resolve和reject, 它们是两个函数。
2)resolve 函数与 reject 函数的作用:
resolve函数:将Promise对象的状态从 “未完成”变为 “成功”(Pending -> Resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去
reject 函数:将Promise对象的状态从 “未完成” 变为 “失败”(Pending -> Rejected),在异步操作失败时调用,并将异步操作报出的错误作为参数传递出去。
3)Promise 实例生成以后,可以用then方法 指定 Resolved 状态和 Rejected 状态的回调函数。
其中 resolve 函数的参数除了正常的值以外,还可能是另一个 Promise 实例,表示异步操作的结果可能是一个值,也有可能是另一个异步操作。
这时 Promise 实例通过 then 指定的回调会等待另一个 Promise 实例状态发生变化后才会进行调用。
上述结果是:promise2 等待 2s 之后输出了 promise 中的值。
这时 promise 的状态会传递给 promise2, promise 的状态决定了 promise2 的状态。
2、Promise实例的状态变化
Promise对象有三种状态
•Pending (进行中)
•Resolved (已完成)
•Rejected (已失败)
Promise的状态变化有上图两条路径:
resolve 方法会使 Promise 对象由 Pending 状态变为 Resolved 状态
reject 方法或异常会使得 Promise 对象由 pending 状态变为 Rejected 状态
对象状态一旦改变,任何时候都能得到这个结果。即状态一旦进入 Resolved 状态或者 Rejected 状态, Promise 对象便不再出现状态变化,同时我们再添加回调会立即得到结果。这点跟事件不一样,事件是发生后再绑定监听,就监听不到了。
3、then方法与错误处理
Promise 实例生成以后,可以用 then 为实例添加状态改变时的回调函数。
getData 函数返回一个 promise 实例,使用 then 为它指定一个 Resolved 状态的回调函数, 异步请求中传给 resolve 的值,将作为回调函数中的参数。当异步请求成功之后,回调函数变会执行输出对应的值。
假设异步请求失败了怎么办? then 其实还可以指定第二个可选的参数,即Rejected 状态的回调函数。
在上述例子中,异步请求成功后,第一个回调函数会执行,如果失败了,第二个回调函数便会执行。
其实我们还可以使用 catch 指定错误时的回调,catch 调用其实等同于使用then(undefined, function) 。
链式调用
我们已经知道了,使用 then 可以为 promise 实例添加状态改变的回调函数。其实,我们还可以使用 then 进行链式调用来指定一组按照顺序执行的回调函数,因为 then 的调用总是会返回 promise 的一个新的实例。
其中后一个 promise 实例会依赖上一个 promise 实例的状态,如果上一个promise 实例状态是 Rejected,则后面的 promise 实例状态也是 Rejected。如果前一个回调函数返回的是一个 promise 对象(有异步操作),这时后一个回调函数会等待该 promise 对象状态发生变化时,才会进行调用。
promise 对象的错误具有冒泡性质,会一直向后传递,直到捕获为止。错误总是会被下一个 catch 语句捕获。
一般来说,不要在 then 方法里面定义 Reject 状态的回调函数,总是使用 catch捕获错误。
Promise与循环
前面提到了 Promise 可以指定一组异步操作顺序执行,那如果我们需要等待一组异步操作之后结束之后再执行呢?Promise 提供了一个很方便的方法Promise.all。
Promise.all 方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
Promise.all 方法接受一个 promise 实例数组作为参数(可以不是数组,但需要具有 iterator 接口), 如果元素不是 Promise 实例,就会先调用Promise.resolve 方法,将参数转为 Promise 实例,再进一步处理。
Promise.all 方法返回的 Promise 实例状态分为两种情况:
实例数组中所有实例的状态都变成 Resolved,Promise.all 返回的实例才会变成 Resolved, 并将 Promise 实例数组的所有返回值组成一个数组,传递给回调函数。
实例数组中某个实例变为了 Rejected 状态,Promise.all 返回的实例会立即变为 Rejected 状态。并将第一个 Rejected 的实例的返回值传递给回调函数。
Promise.race 方法跟 Promise.all 方法差不多。唯一的区别在于该方法返回的Promise 实例并不会等待所有 Proimse 都跑完,而是只要有一个 Promise 实例改变状态,它就跟着改变状态。并使用第一个改变状态实例的返回值作为返回值。
4、promise 兼容性
Promise 已经被写入 ES6 规范中。由下图可以看出,各大浏览器(除IE外)都已开始支持原生 Promise 的使用。但是在低版本浏览器和运行环境中,并不支持 Promise 对象。
要在这些环境中使用 Promise,则需要借助一些兼容 Promise 的类库。ES6 中的 Promise 规范来源于 Promises/A+ 社区,因此,在选择类库时应该考虑对Promise/A+ 兼容性。
Promise 的 Polyfill 类库有很多,笔者经常使用的有(供参考):
es6-promise
bluebird
参考文献:https://promisesaplus.com/
本文作者:张宁(点融黑帮),就职于点融网工程部 social team 创新社。热爱前端,热爱技术。