promise使用详解

359 阅读7分钟

什么是Promise

Promise是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6将其写进了语言标准,统一了用法,原生提供了Promise对象。

Promise对象有以下两个特点。

(1)对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:Pending(进行中)、Resolved(已完成,又称Fulfilled)和Rejected(已拒绝)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态
(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从Pending变为Resolved和从Pending变为Rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。就算改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

3. ES6 中的Promise

先在控制台打印出Promise看看,console.dir(Promise)

由上图可知,Promise是一个函数,自己身上有all、race、reject、resolve等方法,原型上有then、catch等方法
ES6规定,Promise对象是一个构造函数,用来生成Promise实例。
创造了一个Promise实例:

var p = new Promise(function(resolve, reject){
    //做一些异步操作
    setTimeout(function(){
        console.log('执行完成');
        var num = Math.random();
        if(num>=0.5){
            resolve(num);
        } else{
            reject('数字小于0.5');
        }

    }, 2000);

Promise新建后就会立即执行。
上面的代码中,设置了一个2s后执行的定时器。
2s以后输出“执行完成”,然后生成一个随机数,如果数字大于等于0.5,我们认为是“成功”了,调用resolve修改Promise的状态。否则我们认为是“失败”了,调用reject并传递一个参数,作为“失败”的原因。
resolve函数的作用是,将Promise对象的状态从“未完成”变为“已解决”(即从Pending变为Resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;
reject函数的作用是,将Promise对象的状态从“未完成”变为“拒绝”(即从Pending变为Rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。

4. Promise的使用实例
下面是异步加载图片的例子。

function loadImageAsync(url) {
  return new Promise(function(resolve, reject) {
    var image = new Image();

    image.onload = function() {
      resolve(image);
    };

    image.onerror = function() {
      reject(new Error('Could not load image at ' + url));
    };

    image.src = url;
  });

上面代码中,使用Promise包装了一个图片加载的异步操作。如果加载成功,就调用resolve方法,否则就调用reject方法。
Promise.prototype.then()
then方法的第一个参数是Resolved状态的回调函数,第二个参数(可选)是Rejected状态的回调函数。
从表面上看,Promise只是简化了层层回调的写法,而实质上,Promise的精髓是“状态”,用维护状态、传递状态的方式来使得回调函数能够及时调用,它比传递callback函数要简单、灵活的多。

function runAsync1(){
    var p = new Promise(function(resolve, reject){
        //做一些异步操作
        setTimeout(function(){
            console.log('执行完成1');
            resolve('数据1');
        }, 2000);
    });
    return p;            
}
function runAsync2(){
    var p = new Promise(function(resolve, reject){
        setTimeout(function(){
            console.log('执行完成2');
            resolve('数据2');
        }, 2000);
    });
    return p;            
}

function runAsync3(){
    var p = new Promise(function(resolve, reject){
        setTimeout(function(){
            console.log('执行完成3');
            resolve('数据3');
        }, 2000);
    });
    return p;            
}

runAsync1()
.then(function(data){
    console.log(data);
    return runAsync2();
})
.then(function(data){
    console.log(data);
    return runAsync3();
})
.then(function(data){
    console.log(data);
});

以上代码的输出为:
执行完成1
数据1
执行完成2
数据2
执行完成3
数据3
Promise.prototype.catch()
Promise.prototype.catch方法是.then(null, rejection)的别名,用于指定发生错误时的回调函数。

var promise = new Promise(function(resolve, reject) {
  throw new Error('test');
});
promise.catch(function(error) {
  console.log(error);
});

上面代码中,promise抛出一个错误,就被catch方法指定的回调函数捕获。
需要注意的是,catch方法返回的还是一个 Promise 对象,因此后面还可以接着调用then方法。

var someAsyncThing = function() {
  return new Promise(function(resolve, reject) {
    // 下面一行会报错,因为x没有声明
    resolve(x + 2);
  });
};

someAsyncThing()
.catch(function(error) {
  console.log('oh no', error);
})
.then(function() {
  console.log('carry on');
});
// oh no [ReferenceError: x is not defined]
// carry on

上面代码运行完catch方法指定的回调函数,会接着运行后面那个then方法指定的回调函数。如果没有报错,则会跳过catch方法。
Promise.all()
Promise.all方法用于将多个Promise实例,包装成一个新的Promise实例。
下面是一个例子,其中 runAsync1、 runAsync1 和runAsync3上面的例子中已有定义

Promise
.all([runAsync1(), runAsync2(), runAsync3()])
.then(function(results){
    console.log(results);
});

以上代码的输出为:
执行完成1
执行完成2
执行完成3
[“数据1”,” 数据2”,”数据3”]
有了all,你就可以并行执行多个异步操作,并且在一个回调中处理所有的返回数据。

Promise.race()
Promise.race方法同样是将多个Promise实例,包装成一个新的Promise实例。
all方法的效果实际上是「谁跑的慢,以谁为准执行回调」,那么相对的就有另一个方法race「谁跑的快,以谁为准执行回调」。
我们把上面runAsync1的延时改为1秒来看一下:

Promise
.race([runAsync1(), runAsync2(), runAsync3()])
.then(function(results){
    console.log(results);
});

以上代码的输出为:
执行完成1
数据1
执行完成2
执行完成3
在then里面的回调开始执行时,runAsync2()和runAsync3()并没有停止,仍旧再执行。于是再过1秒后,输出了他们结束的标志。
这个race有什么用呢?使用场景还是很多的,比如我们可以用race给某个异步请求设置超时时间,并且在超时后执行相应的操作,代码如下:

//请求某个图片资源
function requestImg(){
    var p = new Promise(function(resolve, reject){
        var img = new Image();
        img.onload = function(){
            resolve(img);
        }
        img.src = 'xxxxxx';
    });
    return p;
}
//延时函数,用于给请求计时
function timeout(){
    var p = new Promise(function(resolve, reject){
        setTimeout(function(){
            reject('图片请求超时');
        }, 5000);
    });
    return p;
}
Promise
.race([requestImg(), timeout()])
.then(function(results){
    console.log(results);
})
.catch(function(reason){
    console.log(reason);
});

requestImg函数会异步请求一张图片,我把地址写为”xxxxxx”,所以肯定是无法成功请求到的。timeout函数是一个延时5秒的异步操作。我们把这两个返回Promise对象的函数放进race,于是他俩就会赛跑,如果5秒之内图片请求成功了,那么遍进入then方法,执行正常的流程。如果5秒钟图片还未成功返回,那么timeout就跑赢了,则进入catch,报出“图片请求超时”的信息。

vue 项目中实战:

三个异步promise方法:

async1(){  
  let p1 = new Promise((resolve,reject)=>{  
    setTimeout(()=>{     
      console.log('执行完成1')     
      resolve('数据1')    
    },1000)  
  })  
  return p1;
},

async2(){  
  let p2 = new Promise((resolve,reject)=>{  
    setTimeout(()=>{     
      console.log('执行完成2')     
      resolve('数据2')    
    },1000)  
  })  
  return p2;
},

async3(){  
  let p3 = new Promise((resolve,reject)=>{  
    setTimeout(()=>{     
      console.log('执行完成3')     
      resolve('数据3')    
    },1000)  
  })  
  return p3;
},

promise.then(catch) 方法:****【promise原型上的方法】

promiseThen(){ 
 //promise.then 如果有一个then执行了reject,就会调用catch捕获错误,不会再调用其他then了  
 //为什么可以持续调用.then?因为每次都return 出去了想要调用的promise对象,就可以调用promise的原型上的then和catch方法了; 
 this.async1().then(res1=>{  
   console.log(res1)  
   return this.async2() 
 }).then(res2=>{   
   console.log(res2)   
   return this.async3() 
 }).then(res3=>{   
   console.log(res3) 
 }).catch(err=>{ 
   console.log('调用then的时候,有一个报错了怎么办',err) 
 })
},

promise.all 方法:【promise身上的方法】

promiseAll(){ 
 //promise.all 的时候,必须全部都执行resolve,才代表成功  
    Promise.all([this.async1(),this.async2(),this.async3()]).then(res=>{
    console.log('执行了promise.all')  
    }).catch(err=>{   
     console.log('执行promise.all的时候有一个失败了')  
   })
},

promise.race 方法:****【promise身上的方法】

promiseRace(){ 
//promise.race 的时候,谁跑的快就以谁为准执行回调  
    Promise.race([this.async1(),this.async2(),this.async3()]).then(res=>{
        console.log('执行了promise.race')  
    }).catch(err=>{   
     console.log('执行promise.race的时候,谁最先输出有一个失败了')  
})
},