浅谈Promise原理与应用

  在JavaScript中,所有代码都是单线程。由于该“缺陷”,JavaScript在处理网络操作、事件操作时都是需要进行异步执行的。AJAX就是一个典型的异步操作

  对于异步操作,有传统的利用回调函数和使用 Promise,二者的对比如下:

//以往回调方式
函数1(function(){
    //代码执行...(ajax1)
    
    函数2(function(){
        //代码执行...(ajax2)
        
        函数3(function(data3){
              //代码执行...(ajax3)
        });
        ...
    });
});

//Promise回调方式:链式调用,可构建多个回调函数。
//例如请求一个ajax之后,需要这个拿到这个ajax的数据去请求下一个ajax
promise().then().then()...catch()

  对比可知,使用传统回调函数方式处理异步操作很复杂。为了解决这样的问题,commonJS引入了Promise概念,很好的解决了JavaScript的异步操作。

概念

  Promise是异步编程的一种解决方案,比传统的解决方案(回调函数和事件)更简单易理解且实用,所以Promise简单来说就是一个容器,里面保存着某个未来才会执行事件(通常为一个异步操作)的结果。

特点

  1. 对象的状态不受外界影响
  2. 一旦状态改变,就不会再变化,任何时候都可以得到这个结果    

语法

//创建Promise实例
    let promise = new Promise( (resolve,reject) =>{
        // 执行相应代码并根据情况调用resolve或者reject
        ...
    } )
    // 在promise的then方法中执行回调
    Promise.then( function(){
        // 第一个参数是返回resolve状态时执行的回调函数
    },function(){
        // 第二个参数是返回reject状态时执行的回调函数
    } )

状态

  Promise对象代表一个异步操作,有三种状态

    pending(等待)resolved(成功状态)rejected(失败状态)

    两种状态改变方式:pending => resolved,pending => rejected

  注:只有异步操作的结果才可以决定当前是哪一种状态,任何其他操作都无法改变这个状态

缺点 

  1. 无法取消 Promise,一旦新建他就会立即执行,中途无法取消;
  2. 如果不设置回调函数,Promise 内部抛出的错误,不会反应到外部;
  3. 当处于 pending 状态时,无法得知目前进展到哪一阶段(刚刚开始还是即将完成)

用法   先看下面这个例子:

1  setTimeout( ()=>{
2         console.log('123');
3     },0 )
4     console.log('456');

  执行结果相信大家都知道,上面console处于异步代码中(即使延迟为0),下面console处于同步代码中,如果想要 ‘456’ 在 ‘123’ 执行结束后再输出呢?

  传统回调函数方式:

1 setTimeout( ()=>{
2         console.log('123');
3         fn();
4     },0 )
5     function fn(){
6         console.log('456')
7     }
8   // 123
9   // 456

  使用 Promise 方式:

 1 let promise = new Promise( (resolve,reject) =>{
 2         setTimeout( ()=>{
 3             console.log('123');
 4             resolve('456');
 5         } ,0)
 6     } )
 7     promise.then(function(data){
 8         // resolve状态
 9         console.log(data);
10     },function(error){
11         // reject状态
12     })
13   // 123
14   // 456

  Promise 实例生成以后,可以用 then 方法分别指定 resolved 状态和 rejected 状态的回调函数。也就是说,状态由实例化时的参数(两个不同状态的回调函数)执行来决定的,根据不同的

状态来执行具体哪个函数。

  注:resolve() 和 reject() 的阐述会传递到对应的回调函数的 data 或者 error,且 then 返回的是一个新的 Promise 实例,也就是或还可以继续 then

链式操作用法   从 表面行看,我们或许会觉得 Promise 只是能够简化传统的层层回调写法。然而,Promise的精髓是 ‘状态’,用维护状态,传递状态的方式来使得回调函数能够及时调用,它比传递 callback 函数要灵活、简单的多。下面为一个 Promise 的一般使用场景:

 1  async1()
 2     .then(function(data){
 3         console.log(data);
 4         return async2();
 5     })
 6     .then(function(data){
 7         console.log(data);
 8         return async3();
 9     })
10     .then(function(data){
11         console.log(data);
12     });
13 
14     function async1(){
15         let p = new Promise(function(resolve,reject){
16             // 异步操作
17             setTimeout(()=>{
18                 console.log('异步任务1执行完成!');
19                 resolve('数据1');
20             },1000);
21         });
22         return p;
23     };
24     function async2(){
25         let p = new Promise((resolve,reject)=>{
26             // 异步操作
27             setTimeout(()=>{
28                 console.log('异步任务2执行完成!');
29                 resolve('数据2');
30             },2000);
31         });
32         return p;
33     };
34     function async3(){
35         let p = new Promise((resolve,reject)=>{
36             // 异步操作
37             setTimeout(()=>{
38                 console.log('异步任务3执行完成!');
39                 resolve('数据3');
40             },3000);
41         });
42         return p;
43     }
44     // 1秒后...
45     // 异步任务1执行完成!
46     // 数据1
47     // 2秒后...
48     // 异步任务2执行完成!
49     // 数据2
50     // 3秒后...
51     // 异步任务3执行完成!
52     // 数据3

  在 then 方法中,可以不用 return Promise实例对象,直接返回数据在后面的 then 中也能够接收到数据

reject用法   在前面的例子中只有 resolve(成功状态)的回调,实际应用中还会有失败状态,reject 就是把 Promise 的状态设置为 rejected ,这样我们就能够在 then 中捕捉到,然后执行相应的回调

 1 let num = 10;
 2     let p1 = function(){
 3         return new Promise((resolve,reject)=>{
 4             if(num <= 5){
 5                 resolve("<=5,走resolve");
 6                 console.log("resolve不能结束Promise");
 7             }
 8             else{
 9                 reject(">5,走reject");
10                 console.log("reject不能结束Promise")
11             }
12         })
13     }
14 
15     p1()
16         .then(function(data){
17             console.log(data)
18         },function(error){
19             console.log(error)
20         })
21     // reject不能结束Promise
22     // >5,走reject

  resolve reject 永远在当前环境的最后面执行,所以后面的同步代码会先执行

  如果 resolve 和 reject 之后还有代码需要执行,最好放在 then 里,然后在 resolve 和 reject 前面写上 return

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

1 p1()
2   .then(function(data){
3     console.log(data)
4   })
5   .catch(function(err){
6       console.log(err)
7   })
8 //reject不能结束Promise
9 //>5,走reject     

Promise.all() 

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

1 const p = Promise.all( [p1,p2,p3] );

  p 的状态由 p1、p2、p3 决定,分为两种情况:

  1. 只有 p1、p2、p3 的状态都为 resolved 时,p 状态才会变成 resolved,此时 p1、p2、p3 的返回值组成一个数组传递给 p 的回调函数
  2. 只要 p1、p2、p3之中有一个状态为 rejected ,p 的状态就变成 rejected,此时第一个状态 为 rejected 的实例的返回值会传递给 p 的回调函数

  由于p 是包含3个 Promise 实例的数组,只有这三个实例状态都为 resolved,或者其中有一个及以上的实例状态为 rejected 时,才会调用 Promise.all 方法后面的回调函数

  如果作为参数的 Promise 实例自己定义了 catch 方法,那么它一旦被 rejected,并不会触发 Promise.all 的 catch 方法,如果没有实例参数定义自己的 catch,就会调用 Promise.all 的 catch 方法

Promise.race()   Promise.race() 方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例

1 const p = Promise.race( [p1,p2,p3] )

  使用该方法时,只要 p1、p2、p3 之中有一个实例率先改变状态,p 的状态就会跟着该实例变化,该实例的返回值传递给 p 的回调函数

Promise.resolve()   在实际应用中有时需要将一个对象转为 Promise 对象,Promise.resolve() 方法就能够实现,该实例的状态为 resolved

const promise = Promise.resolve( '123' );

// 等价于

Promise.resolve( '123' );
new Promise( resolve => resolve( '123' ) )

  Promise.resolve 方法的参数类型有四种情况:

  • 参数为一个 Promise 实例
    • 如果参数就是 Promise 实例,那么 Promise.resolve 将不做任何修改,依然返回该实例
  • 参数为一个 thenable 对象
    • thenable 对象指具有 then 方法的对象,如:
      let thenable = {
           then: function( resolve,reject ){
                resolve( 'aaa' );
      }  
      }
    • Promise.resolve 方法会将这个对象转为 Promise 对象,然后立即执行 thenable 对象的 then 方法
      let thenable = {
        then: function(resolve, reject) {
          resolve( 'aaa' );
        }
      };
      
      let p1 = Promise.resolve(thenable);
      p1.then(function(value) {
        console.log(value);  // aaa
      })

      上面代码中,thenable 对象的 then 方法执行后,对象 p1 的状态就变为 resolved,从而立即执行后面那个 then 方法指定的回调函数,输出 aaa

  • 参数不是具有 then 方法的对象,或者根本不是一个对象
    • 如果参数是一个原始值,或者不是一个具有 then 方法的对象,则 Promise.resolve 方法返回一个新的 Promise 对象,状态为 resolved
      1 const p = Promise.resolve('Hello');
      2 
      3 p.then(function (s){
      4   console.log(s)
      5 });
      6 // Hello

      上面代码生成一个新的 Promise 对象的实例 p,由于字符串 Hello 不属于异步操作( String 对象不具有 then 方法),返回 Promise 实例的状态生成就为 resolved,所以回调函数会立即执行,且 Promise.resolve 方法的参数会同时传给回调函数

  • 参数为空
    • Promise.resolve 方法可以不带参数使用,此时直接返回一个 resolve 状态的 Promise 对象
    • 如果希望得到一个 Promise 对象,比较方便的方法就是直接调用不带参数的 Promise.resolve 方法
      1 const p = Promise.resolve();
      2 
      3 p.then(function () {
      4   // ...
      5 });
    • 上面变量 p 就是一个 Promise 对象,注:立即 resolve 的 Promise 对象,是在本轮“事件循环”(event loop)的结束时,而不是在下一轮“事件循环”的开始时

       1 setTimeout(function () {
       2   console.log('three');
       3 }, 0);
       4 
       5 Promise.resolve().then(function () {
       6   console.log('two');
       7 });
       8 
       9 console.log('one');
      10 
      11 // one
      12 // two
      13 // three

      上面代码中,setTimeout(fn,0)在下一轮“事件循环”开始时执行,Promise。resolve() 在本轮执行,console.log('one')则是立即执行,因此最先输出

Promise.reject() 

  Promise.reject( reason ) 方法也会返回一个新的 Promise 实例,该实例的状态为 rejected

1 const p = Promise.reject('出错了');
2 // 等同于
3 const p = new Promise((resolve, reject) => reject('出错了'))
4 
5 p.then(null, function (s) {
6   console.log(s)
7 });
8 // 出错了

  上面代码生成一个 Promise 对象的实例 p,状态为rejected,回调函数会立即执行

  注:Promise.reject() 方法的参数,会原封不动的作为 reject 的理由,变成后续方法的参数。这一点与 Promise.resolve 方法不一致

 1 const thenable = {
 2   then(resolve, reject) {
 3     reject('出错了');
 4   }
 5 };
 6 
 7 Promise.reject(thenable)
 8 .catch(e => {
 9   console.log(e === thenable)
10 })
11 // true

  上面代码中, Promise.reject 方法的参数为一个 thenable 对象,执行以后,后面 catch 方法的参数不是 reject 抛出的 ‘出错了’ 这个字符串,而是 thenable 对象

两个附加的方法 

  ES6 的 Promise 提供的 API 不是很多,有些有用的方法可以自己部署,下面介绍两个不在 ES6 中,但很有用的方法

done() 

  Promise 对象的回调链,不管以 then 方法或者 catch 方法结尾,要是最后一个方法抛出错误,都有可能无法捕捉到(Promise内部的错误不会冒泡到全局)。因此,可以提供一个 done 方法,总是处于回调链的末端,保证抛出任何可能出现的错误被捕捉

1 asyncFunc()
2   .then(f1)
3   .catch(r1)
4   .then(f2)
5   .done();

实例:

1 Promise.prototype.done = function (onFulfilled, onRejected) {
2   this.then(onFulfilled, onRejected)
3     .catch(function (reason) {
4       // 抛出一个全局错误
5       setTimeout(() => { throw reason }, 0);
6     });
7 };

  上面代码可见,done 方法的使用,可以像 then 方法那样用,提供 resolved 和 rejected 状态的回调函数,也可以不提供任何参数。总之,done 都能够捕捉到任何可能出现的错误,并向全局抛出

finally() 

  finally 方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。它与 done 方法最大的区别是 finally 方法能够接收一个普通的回调函数作为参数,该函数不管怎样都必须执行

实例:服务器使用 Promise 处理请求,然后使用 finally 方法关掉服务器

1 server.listen(0)
2   .then(function () {
3     // run test
4   })
5   .finally(server.stop);
1 Promise.prototype.finally = function (callback) {
2   let P = this.constructor;
3   return this.then(
4     value  => P.resolve(callback()).then(() => value),
5     reason => P.resolve(callback()).then(() => { throw reason })
6   );
7 };

  上面代码中,不管前面的 Promise 是resolved 状态还是 rejected 状态,都会执行回调函数callback

猜你喜欢

转载自www.cnblogs.com/zsp-1064239893/p/11590144.html
今日推荐