闲话
近期项目比较忙,将近一个月没更博了。趁着今天有空来总结一下Promise,顺便安利给大家一本关于Promise的书:《JavaScript Promise迷你书》,该书对于Promise介绍得比较详细,内容通俗易懂。
什么是Promise
Promise是抽象异步处理对象以及对其进行各种操作的组件,来源于Promises/A+社区。它把类似的异步处理对象和处理规则进行规范化,并按照采用统一的接口来编写,而采取规定方法之外的写法都会出错。也就是说,除Promise对象规定的方法(then、catch)以外的方法都不可使用。而回调函数方式,可以自由地定义回调函数的参数。
简单概括:Promise是一个异步编程机制。有很多版本的实现,而ES6 将其写进了语言标准,统一了用法。
Promise 工作流
首先说说其流程,请看如下代码:
function asyncFunction() {
return new Promise(function (resolve, reject) {
setTimeout(function () {
resolve('Async Hello world');
}, 16);
});
}
asyncFunction().then(function (value) {
console.log(value); // => 'Async Hello world'
}).catch(function (error) {
console.log(error);
});
解析:
1.new Promise构造器之后,会返回一个实例化的promise对象
2.asyncFunction返回的promise对象,我们调用它的then方法来设置resolve后的回调函数,catch方法来设置发生错误时的回调函数。
3.该promise对象会在setTimeout之后的16ms时被resolve, 这时then的回调函数会被调用,并输出’Async Hello world’。这种情况不会执行catch的回调(因为promise返回了resolve)。
当然,像promise.then(onFulfilled, onRejected)的方法声明一样, 如果不使用catch方
法只使用then方法的话,如下所示的代码也能完成相同的工作
asyncFunction().then(function (value) {
console.log(value);
}, function (error) {
console.log(error);
});
Promise 状态
了解流程以后,我们再说说状态。实例化的promise对象有三种状态:
pending
初始状态,初始化Promise时,promise对象刚被创建后的初始化状态
fulfilled
完成状态,意味着异步操作成功,此时会调用onRejected
rejected
失败状态,意味着异步操作失败,此时会调用onFulfilled
它只有两种状态可以转化,pending->fulfilled,pending->rejected
只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。
从pending转换为fulfilled或rejected之后,这个promise对象的状态就不会再发生任何变化,同时我们再添加回调会立即得到结果。这点跟事件不一样,如果在事件发生后再绑定监听,就监听不到了。
为什么使用Promise
回调地狱也叫回调金字塔,想必大家应该听过,没听过的看看下边的例子。
例:现在需要发四个请求,asyncFun1成功以后继续发asyncFun2……直到asyncFun4,若中途有失败的,即停止。
回调方式实现:
// 注:asyncFun1第一个参数为成功回调,第二个参数为失败回调。
asyncFun1(function(response1) {
console.log("接口1发送成功");
asyncFun2(function(response2) {
console.log("接口2发送成功");
asyncFun3(function(response3) {
console.log("接口3发送成功");
asyncFun4(function(response4) {
console.log("接口4发送成功");
}, function(response4){
console.log("接口4发送失败");
});
}, function(response3){
console.log("接口3发送失败");
});
}, function(response2){
console.log("接口2发送失败");
});
}, function(response1){
console.log("接口1发送失败");
});
就问你晕不晕吧。实际开发过程中,当业务逻辑复杂的时候,回调的嵌套过多代码复杂度增加,可读性降低、维护起来也复杂、调试也复杂,这就是回调地狱。自从我用了ES6自己写项目以后,再看之前的一些没用Promise的项目,简直头皮发麻,说多是泪。。
使用Promise实现:
function asyncFun1() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("1");
}, 2000)
});
}
function asyncFun2() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("2");
}, 2000)
});
}
function asyncFun3() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("3");
}, 2000)
});
}
function asyncFun4() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("4");
}, 2000)
});
}
// 一般方式
asyncFun1().then((value) => {
console.log("接口" + value + "发送成功");
return asyncFun2();
}).then((value) => {
console.log("接口" + value + "发送成功");
return asyncFun3();
}).then((value) => {
console.log("接口" + value + "发送成功");
return asyncFun4();
}).then((value) => {
console.log("接口" + value + "发送成功");
}).catch((err) => {
console.log("接口" + err + "发送失败")
});
// 使用Promise.all
const p1 = asyncFun1();
const p2 = asyncFun2();
const p3 = asyncFun3();
const p4 = asyncFun4();
const promises = [p1, p2, p3, p4];
Promise.all(promises).then((arrInfo) => {
console.log(arrInfo);
}).catch((err) => {
console.log("接口" + err + "发送失败");
});
这样看起来是不是清爽很多,可读性很高吧?
Promise相关API
关于API的使用,在阮一峰老师的文章中已经介绍得很详细了,在此就不多做描述了。
Promise与Deferred
由于在是给银行做项目,行方还是比较保守的,因此我们项目中仍旧使用ES5,我在优化一些祖传代码的时候,发现先前的前辈有使用Deferred来解决回调地狱的问题。此处所说的Deferred指的是JQuery的Deferred对象,想了解的同学可以参考阮一峰老师的jQuery的deferred对象详解。Deffered总结如下:
(1)$.Deferred(),生成一个deferred对象。
(2)deferred.done(),指定操作成功时的回调函数
(3)deferred.fail(),指定操作失败时的回调函数
(4)deferred.promise(),没有参数时,返回一个新的deferred对象,该对象的运行状态无法被改变;接受参数时,作用为在参数对象上部署deferred接口。
(5)deferred.resolve(),手动改变deferred对象的运行状态为"已完成",从而立即触发done()方法。
(6)deferred.reject(),这个方法与deferred.resolve()正好相反,调用后将deferred对象的运行状态变为"已失败",从而立即触发fail()方法。,除了这些方法以外,deferred对象还有二个重要方法,上面的教程中没有涉及到。
(7)$.when()为多个操作指定回调函数。除了这些方法以外,deferred对象还有二个重要方法,上面的教程中没有涉及到。
(8)deferred.then(),有时为了省事,可以把done()和fail()合在一起写,这就是then()方法。
(9)deferred.always(),这个方法也是用来指定回调函数的,它的作用是,不管调用的是deferred.resolve()还是deferred.reject(),最后总是执行。
Deferred和Promise的关系
Promise来源于Promises/A规范,ES6将其写进了语言标准,统一了用法,提供了原生Promise对象。jQuery.Deferred是一个遵循Promises/A规范的类库。
Deferred 拥有 Promise
Deferred 具备对 Promise的状态进行操作的特权方法(如图)
所谓的能对Promise状态进行操作的特权方法,指的就是能对promise对象的状态进行resolve、reject等调用的方法,而通常的Promise的话只能在通过构造函数传递的方法之内对promise对象的状态进行操作。
两者区别:
Deferred中的方法并无与Promise.race()对应的功能。
相关文章推荐
We have a problem with promises
promises 很酷,但很多人并没有理解就在用了
Js Promise类库对比
Promise Anti patterns
The Revealing Constructor Pattern