ES6之Promise对象

Promise

什么是Promise

ECMAScript 6中的Promise规范来源于Promises/A+社区。Promise是一个用于异步处理对象,其中包含了对异步进行各种操作的组件。Promise把JavaScript中的异步处理对象和处理规则进行了规范化,并按照统一的接口来编写,使用规定方法之外的写法会出现错误。

一个promise有下面三个不同状态:

  • pending待承诺 - 初始状态
  • fulfilled实现承诺 - 一个承诺成功实现状态
  • rejected拒绝承诺 - 一个承诺失败的状态

promise必须实现 then 方法

  • then必须返回一个promise
  • 可以调用多次
  • then方法接受两个参数

Promise基本语法

1、创建一个Promise对象

使用 new 来调用Promise的构造器来进行实例化

new Promise(fn) 返回一个promise对象
在fn 中指定异步等处理
处理结果正常的话,调用resolve(处理结果值)
处理结果错误的话,调用reject(Error对象)

var promise = new Promise(function(resolve, reject) {
// 异步处理
// 处理结束后、调用resolve 或 reject
});

2、了解Promise的状态

用 new Promise 实例化的promise对象有以下三个状态。

"has-resolution" - Fulfilled

resolve(成功)时。此时会调用 onFulfilled

"has-rejection" - Rejected

reject(失败)时。此时会调用 onRejected

"unresolved" - Pending

既不是resolve也不是reject的状态。
也就是promise对象刚被创建后的初始化状态等

关于上面这三种状态的读法,其中 左侧为在 ES6 Promises 规范中定义的术语, 而右侧则是在 Promises/A+ 中描述状态的术语。

3、Promise.then()

对通过 new 生成的promise对象为了设置其值在 resolve(成功) / reject(失败) 时调用的 回调函数 可以使用 promise.then() 实例方法。

promise.then(onFulfilled, onRejected)
resolve(成功)时
onFulfilled 会被调用

reject(失败)时
onRejected 会被调用

onFulfilled、onRejected 两个都为可选参数。
promise.then 成功和失败时都可以使用。 如果只想对异常进行处理时可以采用 promise.then(undefined, onRejected) 这种方式,只指定reject时的回调函数即可。 不过这种情况下 promise.catch(onRejected) 应该是个更好的选择。

var promise = new Promise(function(resolve, reject){
    resolve("传递给then的值");
});
// 写法一
promise.then(function (value) {
    console.log(value);
}, function (error) {
    console.error(error);
});
// 写法二(使用catch)
promise.then(function (value) {
    console.log(value);
}).catch(function (error) {
    console.error(error);
});

4、使用快捷方式

静态方法 Promise.resolve(value) 和 Promise.reject(error) 可以认为是 new Promise() 方法的快捷方式。

4.1 Promise.resolve()

比如 Promise.resolve(1) 可以认为是以下代码的语法糖。

new Promise(function(resolve){
    resolve(1);
});

在这段代码中的 resolve(1)会让这个promise对象立即进入确定(即resolved)状态,并将 1 传递给后面then里所指定的 onFulfilled 函数。

方法 Promise.resolve(value) 的返回值也是一个promise对象,所以我们可以像下面那样接着对其返回值进行 .then 调用。

Promise.resolve(1).then(function(value){
    console.log(value); // 1
});

运用场景:Promise.resolve()在promise对象的初始化或者编写测试代码的时候都非常方便。

4.2 Promise.reject()

比如 Promise.reject(new Error(“出错了”)) 就是下面代码的语法糖形式。

new Promise(function(resolve,reject){
    reject(new Error("出错了"));
});

方法 Promise.reject(error) 的返回值也是一个promise对象,所以可以将错误(Error)对象传递到catch里的函数中。

Promise.reject(new Error("BOOM!")).catch(function(error){
    console.error(error);
});

5、Promise.all()

Promise.all 接收一个 promise对象的数组作为参数,当这个数组里的所有promise对象全部变为resolve或reject状态的时候,它才会去调用 .then 方法。

运用场景: 批量请求

var p1 = Promise.resolve(1),
    p2 = Promise.resolve(2),
    p3 = Promise.resolve(3);
Promise.all([p1, p2, p3]).then(function (results) {
    console.log(results);  // [1, 2, 3]
});
传递给 Promise.all 的promise并不是一个个的顺序执行的,而是同时开始、并行执行的。

可以从以下例子的运行结果看出:

// `delay`毫秒后执行resolve
function timerPromisefy(delay) {
    return new Promise(function (resolve) {
        setTimeout(function () {
            resolve(delay);
        }, delay);
    });
}
var startDate = Date.now();
// 所有promise变为resolve后程序退出
Promise.all([
    timerPromisefy(1),
    timerPromisefy(32),
    timerPromisefy(64),
    timerPromisefy(128)
]).then(function (values) {
    console.log(Date.now() - startDate + 'ms');
    // 約128ms
    console.log(values);    // [1,32,64,128]
});

6、Promise.race()

Promise.race 只要有一个promise对象进入 FulFilled 或者 Rejected 状态的话,就会继续进行后面的处理。

var p1 = Promise.resolve(1),
    p2 = Promise.resolve(2),
    p3 = Promise.resolve(3);
Promise.race([p1, p2, p3]).then(function (value) {
    console.log(value);  // 1
});
进一步分析, 当第一个promise对象变为确定(FulFilled)状态后,它之后的promise对象是否还在继续运行。

// `delay`毫秒后执行resolve
function timerPromisefy(delay) {
    return new Promise(function (resolve) {
        setTimeout(function () {
            console.log(delay)
            resolve(delay);
        }, delay);
    });
}
// 任何一个promise变为resolve或reject 的话程序就停止运行
Promise.race([
    timerPromisefy(1),
    timerPromisefy(10),
    timerPromisefy(20),
]).then(function (value) {
    console.log(value);    // => 1
});
//整体运行结果
1
1
10
20

执行上面代码,我们会看到setTimeout 方法都会执行完毕, console.log 也会分别输出它们的信息。也就是说, Promise.race 在第一个promise对象变为Fulfilled之后,并不会取消其他promise对象的执行。

Promise实例练习

考虑本文一开始的场景,进行小进阶练习,实现每隔一秒依次输出1-10。

//利用for循环 避免一长串的.then写法
function log(i) {
    return new Promise(function(resolve) {
        setTimeout(function() {
            console.log(i)
            resolve();
        }, 1000)
    })
}
function printN(n) {
    var p = log(1);
    for (var i = 2; i <= n; i++) {
        var a = function(x) { // 注意回调函数闭包的问题
            return function() {
                return log(x);
            }
        }
        p = p.then(a(i));
    }
}
printN(10);