ES6的Promise

前言

刚跨入前端的行业的时候,那时候啥也不懂,项目也很小,完全是纯jquery走天下,多个ajax也是一个一个的内嵌的套起来,那时候就觉得这样好难看有点别扭,又不知道咋办。
后来换了公司之后,没事的时候翻翻老大搭的框架,看到了promise

定义

中文翻译过来就是承诺,它是一个对象,里面存储了未来(异步)的才能知道的情况的信息:假如明天下雨了,我就宅家里,假如明天是天晴,我就出去骑自行车。
ES6里面,定义它是一个构造函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var promise = new Promise(function(resolve, reject) {
// ... some code

if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});

promise.then(function(value) {
// success
}, function(error) {
// failure
});

其常见的用法(配合ajax)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
var carService = {
getList: function(o) {
var URL = "/car/getList",
promise = new Promise(function(resolve, reject) {
$.ajax({
url: URL,
method: 'get',
data: o,
success: function(data) {
resolve(data);
},
error: function(xhr, statusText) {
reject(statusText);
}
});
})
;
return promise;
}
};

carService.getList({id: 1001}).then(function(data) {
//成功ajax之后拿到数据之后的操作
}, function(){
//ajax不成功该怎么办
});

Promise.prototype.then()

then方法的第一个参数是Resolved状态的回调函数,第二个参数(可选)是Rejected状态的回调函数。

then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法,而且前一个then方法会将返回结果作为参数传递给后面的then方法。

1
2
3
4
5
6
7
getJSON("/post/1.json").then(function(post) {
return getJSON(post.commentURL);
}).then(function funcA(comments) {
console.log("Resolved: ", comments);
}, function funcB(err){
console.log("Rejected: ", err);
});

Promise.prototype.catch()

Promise.prototype.catch方法是.then(null, rejection)的别名,用于指定发生错误时的回调函数。
另外,then方法指定的回调函数,如果运行中抛出错误,也会被catch方法捕获。

1
2
3
4
5
6
7
getJSON('/posts.json').then(function(posts) {
// 处理成功事件
throw new Error('test');//这里抛出错误,能被后面的catch()接收到
}).catch(function(error) {
// 处理 getJSON 和 前一个回调函数运行时发生的错误
console.log('发生错误!', error);
});

Promise.all()

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

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

上面代码中,Promise.all方法接受一个数组作为参数,p1、p2、p3都是Promise对象的实例,如果不是,就会先调用下面讲到的Promise.resolve方法,将参数转为Promise实例,再进一步处理。(Promise.all方法的参数可以不是数组,但必须具有Iterator接口,且返回的每个成员都是Promise实例。)

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

  • 只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。

  • 只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。

下面是一个具体的例子:

1
2
3
4
5
6
7
8
9
10
// 生成一个Promise对象的数组
var promises = [2, 3, 5, 7, 11, 13].map(function (id) {
return getJSON("/post/" + id + ".json");
});

Promise.all(promises).then(function (posts) {
// ...
}).catch(function(reason){
// ...
});

另一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
const databasePromise = connectDatabase();

const booksPromise = databasePromise
.then(findAllBooks);

const userPromise = databasePromise
.then(getCurrentUser);

Promise.all([
booksPromise,
userPromise
])
.then(([books, user]) => pickTopRecommentations(books, user));

上面代码中,booksPromiseuserPromise是两个异步操作,只有等到它们的结果都返回了,才会触发pickTopRecommentations这个回调函数。

Promise.race()

Promise.all()类似,只不过all()&的意思,race()||的意思。

1
2
3
4
5
6
7
8
const p = Promise.race([
fetch('/resource-that-may-take-a-while'),
new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error('request timeout')), 5000)
})
]);
p.then(response => console.log(response));
p.catch(error => console.log(error));

如果5秒之内fetch方法无法返回结果,变量p的状态就会变为rejected,从而触发catch方法指定的回调函数。

Promise.resolve()

作用是将现有对象转为Promise对象,Promise.resolve方法就起到这个作用。

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

上面代码将jQuery生成的deferred对象,转为一个新的Promise对象。

Promise.resolve等价于下面的写法。

1
2
3
Promise.resolve('foo')
// 等价于
new Promise(resolve => resolve('foo'))

Promise.resolve方法的参数分成四种情况。

  • 如果参数是Promise实例,那么Promise.resolve将不做任何修改、原封不动地返回这个实例。
  • thenable对象指的是具有then方法的对象,比如下面这个对象。

    1
    2
    3
    4
    5
    let thenable = {
    then: function(resolve, reject) {
    resolve(42);
    }
    };

    Promise.resolve方法会将这个对象转为Promise对象,然后就立即执行thenable对象的then方法。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    let thenable = {
    then: function(resolve, reject) {
    resolve(42);
    }
    };

    let p1 = Promise.resolve(thenable);
    p1.then(function(value) {
    console.log(value); // 42
    });

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

  • 如果参数是一个原始值,或者是一个不具有then方法的对象,则Promise.resolve方法返回一个新的Promise对象,状态为Resolved

    1
    2
    3
    4
    5
    6
    var p = Promise.resolve('Hello');

    p.then(function (s){
    console.log(s)
    });
    // Hello

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

  • Promise.resolve方法允许调用时不带参数,直接返回一个Resolved状态的Promise对象。

    所以,如果希望得到一个Promise对象,比较方便的方法就是直接调用Promise.resolve方法。
    上面代码的变量p就是一个Promise对象。

    需要注意的是,立即resolve的Promise对象,是在本轮“事件循环”(event loop)的结束时,而不是在下一轮“事件循环”的开始时。

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

    Promise.resolve().then(function () {
    console.log('two');
    });

    console.log('one');

    // one
    // two
    // three

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

Promise.reject()

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

1
2
3
4
5
6
7
8
var p = Promise.reject('出错了');
// 等同于
var p = new Promise((resolve, reject) => reject('出错了'))

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

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

1
2
3
4
5
6
7
8
9
10
11
const thenable = {
then(resolve, reject) {
reject('出错了');
}
};

Promise.reject(thenable)
.catch(e => {
console.log(e === thenable)
})
// true

Promise.done()(封装的)

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
asyncFunc()
.then(f1)
.catch(r1)
.then(f2)
.done();

//实现方式
Promise.prototype.done = function (onFulfilled, onRejected) {
this.then(onFulfilled, onRejected)
.catch(function (reason) {
// 抛出一个全局错误
setTimeout(() => { throw reason }, 0);
});
};

Promise.finally()(封装的)

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

1
2
3
4
5
6
7
8
9
10
11
12
13
server.listen(0)
.then(function () {
// run test
})
.finally(server.stop);
//实现方式
Promise.prototype.finally = function (callback) {
let P = this.constructor;
return this.then(
value => P.resolve(callback()).then(() => value),
reason => P.resolve(callback()).then(() => { throw reason })
);
};

总结

大致就是这样的,其实基本都看的是阮一峰的ES6