Promise 是 CommonJS 提出的一种规范, 在 ES6 中已经原生支持 Promise 对象, 非 ES6 环境可以用 Bluebird 等库来支持.
0. 引入
在 JS 中任务的执行模型有两种: 同步模式和异步模式.
同步模式: 后一个任务 B 等待前一个任务 A 结束后, 再执行. 任务的执行顺序和任务的排序顺序是一致的.
异步模式: 每一个任务有一个或多个回调函数, 前一个任务 A 结束后, 不是执行后一个任务 B, 而是执行任务 A 的回调函数. 而后一个任务 B 是不等任务 A 结束就执行. 任务的执行顺序, 与任务的排序顺序不一致.
异步模式编程有四种方法: 回调函数 (最基本的方法, 把 B 写成 A 的回调函数), 事件监听 (为 A 绑定事件, 当 A 发生某个事件, 就执行 B), 发布 / 订阅, 以及本文要介绍的 Promise 对象.
Promise 是一个用于处理异步操作的对象, 可以将回调函数写成链式调用的写法, 让代码更优雅, 流程更加清晰, 让我们可以更合理, 更规范地进行异步处理操作. 它的思想是, 每一个异步任务返回一个 Promise 对象, 该对象有一个 then 方法, 允许指定回调函数.
1.Promise 的基本知识
1.1 三种状态
Pending: 进行中, 刚创建一个 Promise 实例时, 表示初始状态;
resolved(fulfilled):resolve 方法调用的时候, 表示操作成功, 已经完成;
Rejected:reject 方法调用的时候, 表示操作失败;
1.2 两个过程
这三种状态只能从 pendeng-->resolved(fulfilled), 或者 pending-->rejected, 不能逆向转换, 也不能在 resolved(fulfilled) 和 rejected 之间转换. 并且一旦状态改变, 就不会再改变, 会一直保持这个结果.
汇总上述, 创建一个 Promise 的实例是这样的:
- // 创建 promise 的实例
- let promise = new Promise((resolve,reject)=>{
- // 刚创建实例时的状态: pending
- if('异步操作成功'){
- // 调用 resolve 方法, 状态从 pending 变为 fulfilled
- resolve();
- }else{
- // 调用 reject 方法, 状态从 pending 变为 rejected
- reject();
- }
- });
- 1.3 then()
用于绑定处理操作后的处理程序, 分别指定 fulfilled 状态和 rejected 状态的回调函数, 即它的参数是两个函数, 第一个用于处理操作成功后的业务, 第二个用于处理操作失败后的业务.
- //then()
- promise.then((res)=> {
- // 处理操作成功后的业务 (即 Promise 对象的状态变为 fullfilled 时调用)
- },(error)=> {
- // 处理操作失败后的业务 (即 Promise 对象的状态变为 rejected 时调用)
- });
- 1.4 catch()
用于处理操作异常的程序, catch() 只接受一个参数
- //catch()
- promise.catch((error)=> {
- // 处理操作失败后的业务
- });
一般来说, 建议不要在 then() 里面定义 rejected 状态的回调函数, 而是将 then() 用于处理操作成功, 将 catch() 用于处理操作异常. 因为这样做可以捕获 then() 执行中的错误, 也更接近同步中 try/catch 的写法:
- //try-catch
- // bad
- promise.then((res)=> {
- // 处理操作成功后的业务
- }, (error)=> {
- // 处理操作失败后的业务
- });
- // good
- promise
- .then((res)=> {
- // 处理操作成功后的业务
- })
- .catch((error)=> {
- // 处理操作失败后的业务
- });
- 1.5 all()
接受一个数组作为参数, 数组的元素是 Promise 实例对象. 只有当参数中的实例对象的状态都为 fulfilled 时, Promise.all( ) 才会有返回.
实例代码 (可直接在浏览器中打开):
- <!DOCTYPE html>
- <HTML lang="en">
- <head>
- <meta charset="UTF-8">
- <title>
- Promise 实例
- </title>
- <style type="text/CSS">
- </style>
- <script type="text/javascript">
- Windows.onload = () = >{
- // 创建实例 promise1
- let promise1 = new Promise((resolve) = >{
- setTimeout(() = >{
- resolve('promise1 操作成功');
- console.log('1')
- },
- 3000);
- });
- // 创建实例 promise1
- let promise2 = new Promise((resolve) = >{
- setTimeout(() = >{
- resolve('promise1 操作成功');
- console.log('2')
- },
- 1000);
- });
- Promise.all([promise1, promise2]).then((result) = >{
- console.log(result);
- });
- }
- </script>
- </head>
- <body>
- <div>
- </div>
- </body>
- </HTML>
结果 (注意看时间):
Promise.all()
代码说明:
1s 后, promise2 进入 fulfilled 状态, 间隔 2s, 也就是 3s 后, promise1 也进入 fulfilled 状态. 这时, 由于两个实例都进入了 fulfilled 状态, 所以 Promise.all() 才进入了 then 方法.
使用场景: 执行某个操作需要依赖多个接口请求回的数据, 且这些接口之间不存在互相依赖的关系. 这时使用 Promise.all(), 等到所有接口都请求成功了, 它才会进行操作.
1.6 race()
和 all() 的参数一样, 参数中的 promise 实例, 只要有一个状态发生变化 (不管是成功 fulfilled 还是异常 rejected), 它就会有返回, 其他实例中再发生变化, 它也不管了.
实例代码 (可直接在浏览器中打开):
- <!DOCTYPE HTML>
- <HTML lang="en">
- <head>
- <meta charset="UTF-8">
- <title>
- Promise 实例
- </title>
- <style type="text/css">
- </style>
- <script type="text/javascript">
- Windows.onload = () = >{
- // 创建实例 promise1
- let promise1 = new Promise((resolve) = >{
- setTimeout(() = >{
- resolve('promise1 操作成功');
- console.log('1')
- },
- 3000);
- });
- // 创建实例 promise1
- let promise2 = new Promise((resolve, reject) = >{
- setTimeout(() = >{
- reject('promise1 操作失败');
- console.log('2')
- },
- 1000);
- });
- Promise.race([promise1, promise2]).then((result) = >{
- console.log(result);
- }).
- catch((error) = >{
- console.log(error);
- })
- }
- </script>
- </head>
- <body>
- <div>
- </div>
- </body>
- </HTML>
结果 (注意看时间):
Promise.race()
代码说明:
1s 后, promise2 进入 rejected 状态, 由于一个实例的状态发生了变化, 所以 Promise.race() 就立刻执行了.
2 实例
平时开发中可能经常会遇到的问题是, 要用 Ajax 进行多次请求. 例如现在有三个请求, 请求 A, 请求 B, 请求 C. 请求 C 要将请求 B 的请求回来的数据做为参数, 请求 B 要将请求 A 的请求回来的数据做为参数.
按照这个思路, 我们可能会直接写出这样的层层嵌套的代码:
- //------ 请求 A 开始 ---------
- $.Ajax({
- success:function(res1){
- //------ 请求 B 开始 ----
- $.Ajax({
- success:function(res2){
- //---- 请求 C 开始 ---
- $.Ajax({
- success:function(res3){
- }
- });
- //--- 请求 C 结束 ---
- }
- });
- //------ 请求 B 结束 -----
- }
- });
- //------ 请求 A 结束 ---------
在请求 A 的 success 后, 请求 B 发送请求, 在请求 B 的 success 后, 请求 C 发送请求. 请求 C 结束后, 再向上到请求 B 结束, 请求 B 结束后, 再向上到请求 A 结束.
这样虽然可以完成任务, 但是代码层层嵌套, 代码可读性差, 也不便于调试和后续的代码维护. 而如果用 Promise, 你可以这样写 (示意代码, 无 Ajax 请求):
此处附上完整可执行代码, 可在浏览器的控制台中查看执行结果:
- <!DOCTYPE HTML>
- <HTML lang="en">
- <head>
- <meta charset="UTF-8">
- <title>
- Promise 实例
- </title>
- <style type="text/css">
- </style>
- <script type="text/javascript">
- Windows.onload = () = >{
- let promise = new Promise((resolve, reject) = >{
- if (true) {
- // 调用操作成功方法
- resolve('操作成功');
- } else {
- // 调用操作异常方法
- reject('操作异常');
- }
- });
- //then 处理操作成功, catch 处理操作异常
- promise.then(requestA).then(requestB).then(requestC).
- catch(requestError);
- function requestA() {
- console.log('请求 A 成功');
- return '下一个是请求 B';
- }
- function requestB(res) {
- console.log('上一步的结果:' + res);
- console.log('请求 B 成功');
- return '下一个是请求 C';
- }
- function requestC(res) {
- console.log('上一步的结果:' + res);
- console.log('请求 C 成功');
- }
- function requestError() {
- console.log('请求失败');
- }
- }
- </script>
- </head>
- <body>
- <div>
- </div>
- </body>
- </HTML>
结果如下:
实例
可以看出请求 C 依赖请求 B 的结果, 请求 B 依赖请求 A 的结果, 在请求 A 中是使用了 return 将需要的数据返回, 传递给下一个 then() 中的请求 B, 实现了参数的传递. 同理, 请求 B 中也是用了 return, 将参数传递给了请求 C.
3. 小结
本文主要介绍了 Promise 对象的三个状态和两个过程."三个状态" 是: 初始化, 操作成功, 操作异常,"两个过程" 是初始化状态到操作成功状态, 和初始化状态到操作异常状态. 除此之前, 还有两种实例方法: then(),catch() 来绑定处理程序. 类方法: Promise.all(),Promise.race(). 如有问题, 欢迎指正.
来源: https://www.qcloud.com/developer/article/1359152