ES6中的Promise对象

1,027 阅读5分钟

什么是Promise对象?

Promise是异步编程的一种解决方案,比起传统的解决方案(回调函数和事件),它显得更加的强大和方便(具体请看下文)。从语法上来讲,Promise是一个对象,从它可以获取异步操作的消息。Promise对象提供统一的API,各种异步操作都可以用同样的方法进行处理。

Promise有什么用?

大家一致会回答,解决回调地狱。那什么又是回调地狱?它有什么缺点?

如果要执行四个步骤,依次执行,那么传统我们可能会这么做:

step1(function (value1) {
    step2(value1, function(value2) {
        step3(value2, function(value3) {
            step4(value3, function(value4) {
                // Do something with value4
            });
        });
    });
});

为了解决这种看起来不很舒服,写起来更不舒服的写法,我们可能会想起链式写法:

var obj = {
    stepOne: function () {
        console.log('one');
        return this;
    },
    stepTwo: function () {
        console.log('two');
        return this;
    },
    stepThree: function () {
        console.log('three');
        return this;
    }
}

obj.stepOne().stepThree().stepTwo();

这种写法的核心在于,每次都返回this。 这样的好处在于:

  • 每一个操作都是独立的一个函数
  • 可以组装,也就是我们可以控制执行的流程

那如果我们还需要一些其他需求的时候呢?比如:

  • 如果上一步的结果作为下一步的输入就更好了
  • 如果出错我能够捕捉就更好了
  • 如果我在函数中能够控制流程就好了 ....

这个时候,就需要用到我们强大的Promise了

基础用法

先来看看怎么使用吧!直接上代码:

const p1 = new Promise(function(resolve, reject) {
    // 在这里做一些逻辑处理
    // 如果异步操作成功的时候,调用resolve
    if (true) {
        resolve('success');
    } else {
        // 操作失败后我们就调用reject()
        reject('fail');
    }
})

p1.then((val) => {
    console.log(val); // success
}, (err) => {
    console.log(err)
})

我们可以看到Promise是一个构造函数,用来生成Promise实例,它接收一个函数作为参数,这个函数有两个参数——resolve和reject(它们也是两个函数,已经由JavaScript引擎提供,不用自己部署)。

我们经常会在这个函数里处理一些逻辑,如果处理成功后我们会执行resolve(将状态从“未完成”转为“完成”),失败后执行reject(将状态从“未完成”转换为“失败”)。

三个状态

  • pedding(未完成)
  • resolved(完成)
  • rejected(失败)

实例生成之后,我们可以使用then方法指定resolved和rejected状态的回调函数。像上面的代码,就相当于执行第一个回调函数,第二个不会执行(这个时候其实不提供第二个参数也是可以的)。

如果是执行失败,状态变成reject的时候,就会执行第二个回调函数,就会输出'fail'。但是其实我们不是很推荐这样的写法,我们可以将第二个回调函数写catch方法的形式。

const p1 = new Promise(function(resolve, reject) {
    // 在这里做一些逻辑处理
    // 如果异步操作成功的时候,调用resolve
    if (false) {
        resolve('success');
    } else {
        // 操作失败后我们就调用reject()
        reject('fail');
    }
})

p1.then((val) => {
    console.log(val);
}).catch((err) => {
    console.log(err); // fail
})

这种写法类似于try...catch...,更加易于我们理解。这个时候我们其实就已经解决了出错我们能够捕捉的问题了。

控制代码的执行顺序

采用链式的then,可以指定一组按照次序调用的回调函数。这个时候前面的一个返回的可能是另外一个Promise对象(也就是说有异步操作)。这样后面的这个Promise就依赖于前面Promise对象的状态。

const p1 = new Promise(function(resolve, reject) {
    console.log(1);
    setTimeout(function() {
        console.log(2);
        resolve();
    }, 1000);
})

const p2 = new Promise(function(resolve, reject) {
    console.log(3);
    resolve(p1);
})

p2.then(() => {
    console.log(4);
}).then(() => {
    console.log(5);
})

输出结果

以上代码,我们应该注意一点p2的resolve方法将p1作为参数,也就是说p2的执行依赖于p1的执行。当p2准备好的时候,p1可能还没准备好,这个时候p2就得等p1。

另外,需要注意的一点就是,then方法返回的是一个新的Promise实例(注意,不是之前的Promise实例),因此可以采用链式写法,即then方法之后再调用另一个then方法。这样,我们就可以实现了上面的需求了!

Promise的几个重要方法

下面简单介绍一下Promise对象的几个方法,我们经常也会用到。

Promise.all()

直接上代码:

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

Promise.all方法接收一个数组作为参数,p1、p2、p3都是Promise实例。这个时候p的状态由p1、p2、p3。它们的依赖机制类似于电路中的串联,现在我们使用三条线(就是p1、p2、p3)进行串联,p就是最后灯泡,灯泡要亮的话,三个都要成功,只要其中的一个没有成功,那么p的状态就是rejected。

Promise.race()

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

这个方法也是接受一个数组作为参数。race的英文意思就有竞赛的意思。上面的代码中表示,p1、p2、p3中哪个胜出(最新状态发生改变,不管是失败还是成功),那么p的状态就会跟着它改变。

Promise.resolve()

作用:将现有对象转换成Promise对象,这样我们就可以调用then方法等

const jsPromise = Promise.resolve($.ajax('/whatever.json'));

Promise.reject()

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

const p = Promise.reject('出错了');
// 等同于
const p = new Promise((resolve, reject) => reject('出错了'))

p.then(null, function (s) {
  console.log(s)
});
// 出错了

参考: https://cnodejs.org/topic/560dbc826a1ed28204a1e7de

http://es6.ruanyifeng.com/#docs/promise