1. Promise简介
Promise是ES6新引入的对象,是新增加的异步处理手段。
在Javascript 中在异步后要进行操作,最常用的手段是回调,例如:
ajax({success: function(data){
// success logic
}, fail: function(err) {
// error logic
}})
但是回调存在一些被大家所熟知的问题,类似回调地狱(回调嵌套),而且回调存在的一个很严重的问题,当我们把回调作为方法传递给异步方法以后,我们拿不到任何于这个异步回调相关的句柄,也就是说,我们只能等待回调通过事件循环机制去调用,拿不到和回调相关的任何信息,将回调的控制权转移给了异步方法。
Promise和回调的区别在于Promise在创建以后会返回一个句柄,而我们通过这个句柄,就可以对异步结果进行处理,主动权回到了我们手上。
举个书上简单的例子:我们去麦当劳吃饭(麦当劳绝对没给我赞助。。。),去点单,然后付款,如果采用回调的方式,付款后就什么都不给你,你需要一直等着叫你,叫到你了马上去拿食物,而采用Promise的方式,付款后会给你一个小票,只要食物准备好了,你拿着这个小票可以随时的来获取食物。
也就是说,对于回调来说,回调方法本身就是异步调用结果,而对于Promise来说,对于异步调用结果是类似于事件注册和事件监听(小票)。
2. Promise的创建
2.1 new Promise()
使用new Promise()
创建Promise
需要传递一个有两个参数的函数
new Promise((resolve, reject) => {
setTimeout(_=>{resolve(1)}, 1000);
})
这里有几点需要注意的内容:
Promise
中的函数会立即执行resolve
和reject
将产生决议值,只会决议一次,产生一个决议值,且决议值在决议后不会发生变化resolve
会尝试展开参数的值,展开按照以下规则:
3.1 如果参数是Promise
类型或者thenable
(下面会说明)类型,那么会尝试进行展开该参数,直到拿到最后的决议值
3.2 如果参数是非上述情况,则直接将该值作为决议值reject
会将参数值作为决议值直接返回
分别做一下说明:
Promise
中的函数会立即执行,也就是说,Promise
函数本身不是异步的,但是其中可以包含异步过程
console.log(1);
new Promise((resolve, reject)=> {
console.log(2);
setTimeout(_=>{console.log(4);},1000)
})
console.log(3);
// 输出结果是 1,2,3,4
决议值,是异步方法后返回的结果,一个Promise
只能产生一个决议值,而且只能决议一次,一旦决议就会永远保持这个状态,成为不变值。也就是通过resolve
或者reject
方法处理后的结果,只会将第一次的结果作为决议值
// 只能决议一次
var p1 = new Promise((resolve, reject) => {
resolve(1);
resolve(2);
})
p1.then(fulfill => {
console.log(fulfill); // 1
});
// 决议后值不变, 可以多次调用
p1.then(fulfill => {
console.log(fulfill); // 1
});
p1.then(fulfill => {
console.log(fulfill); // 1
})
thenable
类型是指含有then
方法的对象,包括原型链上还有then
方法的对象(then
方法可以有两个参数,分别对应了resolve
和reject
)
var obj = {
then(cb, ecb) {
cb(1);
}
}
resolve
展开可以通过下面的例子理解
var obj = {
then(cb, ecb) {
cb(1);
}
}
var p1 = new Promise((resolve, reject) => {
resolve(1)
})
new Promise((resolve, reject) => {
resolve(obj); // 或者 resolve(p1)
}).then(fulfill => {
console.log(fulfill); // 1
})
reject
不进行展开可以通过下面的例子理解
var obj = {
then(cb, ecb) {
cb(1);
}
}
var p1 = new Promise((resolve, reject) => {
resolve(1)
})
new Promise((resolve, reject) => {
reject(obj); // 或者 reject(p1)
}).then(null, reject => {
console.log(reject === obj); // true,或者console.log(reject === p1)
})
2.2 Promise.resolve()
Promise.resolve()
会接收一个对象,并返回一个决议的Promise
,根据接收对象不同,返回的Promise
的决议值会有不同操作
- 如果接受对象是
Promise
类型,那么将直接返回该对象- 如果接受对象是
thenable
类型,那么会尝试展开,将最后的决议值作为返回Promise
的决议值- 非以上两种情况,将该值作为返回
Promise
的决议值
这里有两个地方需要注意
-
Promise
类型作为参数和之前resolve
的处理方式不同 - 这里的
Promise.resolve
产生的Promise
决议值不一定就是成功的,也有可能是失败的
用例子是最好说明的:
var obj = {
then(cb, ecb) {
cb(1);
}
}
var p1 = new Promise((resolve, reject) => {
resolve(1)
})
// Promise对象直接返回
console.log(p1 === Promise.resolve(p1)); // true
// 展开thenable
Promise.resolve(obj).then(fulfill => {
console.log(fulfill); // 1
})
// 直接作为决议值
Promise.resolve(obj).then(fulfill => {
console.log(fulfill); // 1
})
// 返回的决议值为失败
Promise.resolve(new Promise((resolve, reject)=>{
reject(1)
})).then(fulfill => {
// 不会运行到这里
console.log('fulfill');
console.log(fulfill);
}, reject => {
// 输出结果
console.log('reject');
console.log(reject);
})
2.3 Promise.reject()
Promise.reject()
也是接收一个参数,会产生一个决议值一定为reject
的Promise
,且和reject
一样,将参数作为返回Promise
的决议值
var obj = {
then(cb, ecb) {
cb(1);
}
}
var p1 = new Promise((resolve, reject) => {
resolve(1)
})
console.log(p1 === Promise.reject(p1)); // false,因为相当于返回Promise(p1)
// 展开thenable
Promise.reject(obj).then(fulfill => {
// 不会运行到这里
}, reject => {
console.log(obj === reject); // true
})
3. Promise的使用
在获取到Promise
以后,调用then
方法可以对决议值进行操作,then
方法调用的时候包含两个参数,分别接收成功和拒绝时候的决议值,和我们经常将回调拆分为成功回调合失败回调的做法类似。
var p1 = new Promise((resolve ,reject) => {
resolve(1);
});
p1.then(fulfill => {
// 如果决议成功将调用
console.log('fulfill');
console.log(fulfill); // 1
}, reject => {
// 如果决议失败将调用
console.log('fulfill');
console.log(reject);
})
4. Promise的模式
Promise
模式是为了处理多个异步事件之间的关系
4.1 Promise.all()
Promise.all()
接受一个数组作为参数,他会等待所有的Promise
都完成以后,返回一个决议值,这个决议值是根据传递参数数组顺序,将每一个Promise
的决议值放倒对应的位置,如果数组中任何一个Promise
拒绝,则会将该拒绝值作为结果,如果是空数组,则会立即进行决议
var obj = {
then(cb, ecb) {
setTimeout(_=> cb(2), 100);
}
}
var p1 = new Promise((resolve, reject) => {
setTimeout(_=> resolve(3), 100);
})
Promise.all([1, obj, p1]).then(fulfill => {
// 可以利用解构来获取到对应的值 var [v1,v2,v3] = fulfill
console.log(fulfill.length === 3); // true
console.log(fulfill[0] === 1); // true
console.log(fulfill[1] === 2); // true
console.log(fulfill[2] === 3); // true
})
// 有任何拒绝,则拒绝值作为结果
Promise.all([1, obj, p1, Promise.reject(4)]).then(fulfill => {
}, reject => {
console.log(reject === 4); // true
})
// 空数组立即决议
Promise.all([]).then(fulfill => {
console.log('fulfill');
})
4.2 Promise.race()
Promise.race()
和Promise.all()
类似,同样接受一个数组作为参数,但是区别在于Promise.race()
只会返回最快进行决议的结果作为结果值,且如果为空数组则永远不会进行决议(相当于永远在等待最快的决议的结果,但是空数组一直没有决议,所以会一直等待)
var p1 = new Promise((resolve, reject) => {
setTimeout(_=> resolve(1), 100);
})
var p2 = new Promise((resolve, reject) => {
setTimeout(_=> resolve(2), 150);
})
var p3 = new Promise((resolve, reject) => {
setTimeout(_=> reject(3), 50);
})
Promise.race([p1, p2]).then(fulfill => {
console.log('fulfill');
console.log(fulfill === 1); // true, 因为p1会比p2先进行决议
})
Promise.race([p1, p3]).then(fulfill => {
}, reject => {
console.log('reject');
console.log(reject === 3); // true, 因为p3会比p1先进行决议,且决议拒绝
})
// 空数组永远不会决议
Promise.race([]).then(fulfill => {
console.log('fulfill'); // 永远不会执行
}, reject => {
console.log('reject'); // 永远不会执行
})
5. Promise的特点
5.1 Promise链
Promise
在执行then
方法以后,会返回一个Promise
对象,这个新的Promise的决议值是根据上一个then
方法中的return
值来确定的,于是形成了一个Promise
的链路
var p1 = new Promise((resolve, reject) => {
setTimeout(_=> resolve(1), 100);
});
var p2 = p1.then(fulfill => {
console.log(fulfill); // 1
return 2; // 返回的值会作为链路上新的Promise的决议值
});
// 其实p2相当于
var p2 = new Promise((resolve, reject) => {
resolve(2);
});
5.2 Promise异常处理
Promise
和回调一样,因为决议过程是异步,所以没办法使用try catch
来捕获异常,Promise
链路上如果产生异常,那么会认为Promise
决议被拒绝,该异常会作为拒绝的决议值,放倒链路上下一个Promise
中。
var err = new Error(1);
var p1 = new Promise((resolve, reject) => {
throw err;
setTimeout(_=> resolve(1), 100);
});
var p2 = p1.then(fulfill => {
console.log('fulfill'); // 不会执行到这里
}, reject => {
console.log(reject === err); // true
});
也就是说p1
产生的异常,自身并不能进行处理,必须要使用链路上下一个Promise
也就是p2
中处理,如果不进行处理,那么该错误就会被丢掉了
同时,对于异常处理可以使用catch
方法,接收一个参数,相当于then(null, reject)
var err = new Error(1);
var p1 = new Promise((resolve, reject) => {
throw err;
setTimeout(_=> resolve(1), 100);
});
var p2 = p1.catch(reject => {
console.log(reject === err); // true
});
6. 总结
Promise
比起回调来说,可以减少回调地狱带来的代码风格问题,也将控制权重新回到了我们手中。
7. 参考
《你不知道的Javascript(中篇)》
MDN-Promise