Zerlinda's Blog

浅谈ES6异步Promise对象

在前一篇文章浅谈JavaScript运行机制Event Loop中讲到Event Loop避免了在浏览器端因为单线程同步执行任务造成的前一个任务未完成,后一个任务必须一直等待因而造成的浏览器阻塞问题。在浏览器端,耗时很长的操作都应该异步执行,避免浏览器失去响应。这就是JavaScript的“异步编程”。异步编程的方法大致包括:直接回调、pub/sub模式(事件模式)、异步库控制库(例如async、when)、promise、Generator等。

Promise是ES6提出的一种解决异步编程的方案,原生提供了Promise对象。

认识promise

Promise,顾名思义“承诺”,一个 Promise表示一个现在、将来或永不可能可用的值。语法上来讲,Promise 是一个代理的对象(代理一个值),被代理的值在Promise对象创建时是未知的。但是Promise对象能够获取异步操作的执行结果(成功或者失败)并为之绑定相应的方法。 最终Promise对象返回的同样是一个Promise对象

一个 Promise有以下几种状态:

pending: 初始状态,任务进行中,未执行或拒绝。

fulfilled: 意味着操作成功。

rejected: 意味着操作失败。

pending 状态的 Promise 对象最终只能有两种状态,一种是fulfilled,一种是rejected。一旦异步任务操作成功,pending状态立马变成fulfilled。反之操作失败,Promise由pending状态变成rejected。而且这种最终的状态都不会再改变

Promise官方提供6个方法:

Promise.prototype.catch()
Promise.prototype.then()
Promise.all()
Promise.race()
Promise.reject()
Promise.resolve()

Promise的语法

new Promise(

    /* executor */

    function(resolve, reject) {…}

);

executor是一个带有resolve和reject两个参数的函数 。executor 函数在Promise的创建时就立即执行。而异步任务一旦完成,就调用resolve函数来解决promise,并将异步操作的结果,作为参数传递出去。反之操作失败,就调用reject函数,并同样将异步操作报出的错误,作为参数传递出去。注意的是,在executor函数运行时抛出任何一个错误,都会导致promise状态变为rejected,这也意味着异步操作失败。

更常见的是下面这种形式:

new Promise(
  /* executor */
function(resolve, reject) {
   // ... some code
   if (/* 异步操作成功 */){
     resolve(value);
   } else {
     reject(error);
   }
}
);

在上面的代码形式中,通过if…else…来区分异步操作成功与否仅仅是为了形象理解Promise,实际代码中,resolve和reject是完全由我们自己决定出现的形式和位置,换言之,我们可以自己定义操作成功和操作失败的条件,从而为之绑定对应的处理函数。

上一个简单的例子来直观感受一下:

var myFirstPromise = new Promise(function(resolve, reject){
    //当异步代码执行成功时,我们才会调用resolve(...), 当异步代码失败时就会调用reject(...)
    //在本例中,我们使用setTimeout(...)来模拟异步代码,实际编码时可能是XHR请求或是HTML5的一些API方法.
    setTimeout(function(){
        resolve("成功!");    //代码正常执行
    }, 250);
});
myFirstPromise.then(function(successMessage){
    //successMessage的值是上面调用resolve(...)方法传入的值"成功!".
    //successMessage参数不一定非要是字符串类型,这里只是举个例子
    console.log("Yay! " + successMessage);    // Yay! 成功!
});

Promise.prototype.then()

Promise实例生成以后,可以用then方法分别指定fulfilled状态和rejected状态的回调函数。

Promise.prototype.then(onfulfilled(result)[, onrejected(error)])
p.then(function(value) {
   // fulfillment
  }, function(reason) {
  // rejection
});

fulfilled状态调用的是then 的 onfulfilled 方法,rejected状态调用 then 的 onrejected 方法。then方法包含两个参数:onfulfilled 和 onrejected,它们都是 Function 类型,其中都包含着从executor函数中resolve和reject绑定方法传出来的参数。需要说明的是onrejected函数是可选的,不指定onrejected函数,错误将被接下来讲到的catch函数捕获。

function promise(){
   var a = new Promise((resolve,reject)=>{
      var timer = setTimeout(()=>reject({obj: 3}), 3000)
   })
   return a;
}
promise().then(result=>console.log("success",result),error=>console.log("failed",error))
//failed Object {obj: 3}

上面的代码执行到timer后Promise状态变为rejected,因此,then方法执行的是rejected状态对应的reject方法。反之如果timer的回调指定是的resolve方法,那么执行到timer后Promise的状态就会变为fulfilled,then方法就会执行resolve方法了。

Promise.prototype. catch ()

Promise的原型上定义了一个catch方法,catch方法可以用于promise组合中的错误处理

Promise.prototype.catch(onRejected);
p.catch(function(reason) {
   // 拒绝
});

如果异步操作抛出错误,Promise状态就会变为Rejected,就会调用catch方法指定的回调函数,处理这个错误。另外,then方法指定的回调函数,如果运行中抛出错误,也会被catch方法捕获。同样,当不指定.then函数的第二个参数onrejected函数时,错误信息会被catch函数捕获,这时候的作用与onrejected函数是同等的。

function promise(){
   var a = new Promise((resolve,reject)=>{
      setTimeout(()=>resolve({obj: 3}), 3000)
   })
   return a;
}
var pro = promise();
pro. then(re=>{
   console.log("success:", re);
   x = x + 1;
}).catch(error => console.log("error: ", error));
//success: Object {obj: 3}
//error:  ReferenceError: x is not defined
//at pro.then.re (:10:8)

在上面.then()方法中举得例子异步操作失败也可以换成catch方法:

function promise(){
   var a = new Promise((resolve,reject)=>{
      setTimeout(()=>reject({obj: 3}), 3000)
   })
   return a;
}
promise().then(result=>console.log("success",result)).catch(result=>console.log("failed",result))
//failed Object {obj: 3}

因为 Promise.prototype.then 和 Promise.prototype.catch 方法返回 promise 对象自身, 所以它们可以被链式调用。

function promise(){
   var a = new Promise((resolve,reject)=>{
      setTimeout(()=>resolve({obj: 3}), 3000)
   })
   return a;
}
var pro = promise();
pro.then(re=>{
   console.log("success:", re);
   return promise();
}).then(re=>{
   console.log("success:", re);
   return promise();
}).then(re=>{
   console.log("success:", re);
   return promise();
}).then(re=>{
   console.log("success:", re);
   return promise();
})
//success: Object {obj: 3}
//success: Object {obj: 3}
//success: Object {obj: 3}
//success: Object {obj: 3}

上面的代码,每次在then函数执行完,返回的都是同一个新的Promise对象,所以每次都能输出同样的结果。最终每隔三秒输出一次结果。

当然,then函数即使不明确指定返回promise对象,它自身返回的也是一个promise对象。

function promise(){
   var a = new Promise((resolve,reject)=>{
      setTimeout(()=>resolve({obj: 3}), 3000)
   })
   return a;
}
var pro = promise();
pro.then(re=>{
   console.log("success:", re);
   return promise();
}).then(re=>{
  console.log("success: ", re);
}).then(re=>{
  console.log("success: ", re);
}).then(re=>{
  console.log("success: ", re);
})
//success: Object {obj: 3}
//success:  Object {obj: 3}
//success:  undefined
//success:  undefined

不同于上面的是,then自身返回的promise对象在继续使用.then链式调用时,其中的onfulfilled方法的参数是undefined(因为我们压根没传)。

下面一张图能够直观感受Promise链式调用的过程:

注: 如果一个promise对象处在fulfilled或rejected状态而不是pending状态,那么它也可以被称为settled状态。

看一个综合的例子:

function promise(){
   var a = new Promise((resolve,reject)=>{
console.log("carry on");
      var timer1 = setTimeout(()=>resolve({obj: 3}), 3000)   
      var timer2 = setTimeout(()=>{console.log("going on");  reject({obj: 3})}, 3000)
   })
   return a;
}
setTimeout(() => console.log("timeout"), 4000);
promise ().then(result=>console.log("success",result), result=>console.log("failed",result));
console.log("origin");
// carry on
//origin
//success Object {obj: 3}
//going on
//timeout

上面的代码作为演示的例子实际当中并没有什么用,但是却能帮我们很好的理解Promise。思考下面三行输出结果及顺序:

setTimeout(() => console.log("timeout"), 4000);
promise ().then(result=>console.log("success",result), result=>console.log("failed",result));
console.log("origin");

在promise函数中,我们创建了一个Promise实例a,Promise实例一经创建便立即执行匿名函数,因此同步任务console.log("carry on")能够首先立马输出对应的结果。匿名函数中创建了两个异步定时器任务timer1和timer2,规定当执行到timer1意味着异步操作成功,使用resolve函数来解决;同理执行到timer2意味着操作失败。回过头来当执行完timer1,Promise的状态已经是fulfilled,不管后续发生什么,都不会再改变状态,因此,timer2即使执行也改变不了Promise的状态,console.log("going on")虽然能够输出,但对应rejected状态的onrejected函数永远也执行不了。根据Event Loop机制,以上代码的输出顺序一目了然。

Promise.resolve()

Promise.resolve(value):解析value并返回一个promise对象。

通常是有三种情况:

Promise.resolve(promise);

如果参数是Promise实例,那么Promise.resolve将直接返回该实例

Promise.resolve(thenable);

如果参数是个thenable(即带有then方法),返回的promise会采用它的最终状态(指resolved/rejected/pending/settled);

var thenable1 = {
  then: function(resolve, reject) {
    reject(11);
  }
};
Promise.resolve(thenable1).then().catch(re=>console.log(re))
//11

以上代码返回了一个reject状态的Promise并执行onrejected方法。

Promise.resolve(value);

当参数既不是promise对象也不是thenable,直接返回一个resolved状态的promise对象,value作为resolve方法参数传递给then。

Promise.resolve("Success").then(function(value) {
  console.log(value); // "Success"
}, function(value) {
  // 不会被调用
});
var p = Promise.resolve([1,2,3]);
p.then(function(v) {
  console.log(v[0]); // 1
});
var p = Promise.resolve();
p.then(function(v) {
  console.log(v); // undefined
});

第三种情况因为是同步任务,所以执行的时候放在本轮Event Loop结束时而不是下轮开始之前。

Promise.reject(reason)

Promise.reject(reason):返回一个用reason拒绝的Promise。

它与resolve不同的是,Promise.reject(reason)返回的一定是一个rejected状态的Promise对象,其中reason不论是何种类型数据都作为rejected的参数传入then方法的onrejected函数。也就是说,即使reason是thenable类型并且then方法中指定reject,thenable也是作为一个整体传入then方法的onrejected中而不会执行。

Promise.reject("Testing static reject").then(function(reason) {
  // 未被调用
}, function(reason) {
  console.log(reason); // "测试静态拒绝"
});
//Testing static reject
Promise.reject(new Error("fail")).then(function(error) {
  // 未被调用
}, function(error) {
  console.log(error); // 堆栈跟踪
});
// Error: fail
var thenable1 = {
  then(resolve, reject) {
    reject('failed');
  }
};

Promise.reject(thenable1)
.catch(e => {
  console.log(e)
})
//Object {then: function}

Promise.all()

Promise.all(iterable) 参数iterable是一个可迭代对象(具有iterator接口),通常是Array。它返回一个新的Promise实例。

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

上面的代码中,p1、p2、p3都是Promise对象的实例,如果不是,就会调用Promise.resolve方法将参数转为Promise。p的状态由p1、p2、p3决定,分成两种情况:当p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数;只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。

function timeout(num){
    var promise = new Promise((resolve, reject) => {
       setTimeout(() => resolve(num), num * 1000)
    })
    return promise;
}
var p = Promise.all(
   [1, 2, 3, 4, 5].map(num =>{
       return timeout(num)
   })
).then((e) => console.log("over", e))
//over [1, 2, 3, 4, 5]

Promise.race()

Promise.race(iterable):iterable参数与Promise.all方法完全一样,返回一个 promise。

var p = Promise.race([p1, p2, p3]);

上面代码中,只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。

var p1 = new Promise(function(resolve, reject) { 
    setTimeout(resolve, 500, "one"); 
});
var p2 = new Promise(function(resolve, reject) { 
    setTimeout(resolve, 100, "two"); 
});

Promise.race([p1, p2]).then(function(value) {
  console.log(value); // "two"
  // 两个都完成,但 p2 更快
});

var p3 = new Promise(function(resolve, reject) { 
    setTimeout(resolve, 100, "three");
});
var p4 = new Promise(function(resolve, reject) { 
    setTimeout(reject, 500, "four"); 
});

Promise.race([p3, p4]).then(function(value) {
  console.log(value); // "three"
  // p3 更快,所以它完成了              
}, function(reason) {
  // 未被调用
});

var p5 = new Promise(function(resolve, reject) { 
    setTimeout(resolve, 500, "five"); 
});
var p6 = new Promise(function(resolve, reject) { 
    setTimeout(reject, 100, "six");
});

Promise.race([p5, p6]).then(function(value) {
  // 未被调用             
}, function(reason) {
  console.log(reason); // "six"
  // p6 更快,所以它失败了
});

发表评论

电子邮件地址不会被公开。 必填项已用*标注