Promise 总览:
从中国网民数量急剧增加开始,前端脚本中同步处理就逐渐不够看了,异步处理就被大量使用。这时候,“地狱回调” 就出现了,一层摞一层,让人头大,代码维护的成本变得极大;所以各式各样企图避免“地狱回调”的途径也就应运而生。到了ES6 多个提供异步操作的API 也就出来了,譬如:Promise、Generator(async函数是Generator的语法糖);
Promise 的特点:
- 一个 Promise 对象就是一个异步操作,这个异步操作存在三种状态,分别是:处理中、成功和失败。Promise 的状态外界无法去影响,只由异步操作的结果决定;
- 异步操作一旦有了结果,Promise对象的状态就不会再改变,这时候再去给Promise 对象添加回调函数,立马得到的也是这个结果;
- Promise 对象一旦创建出来就会执行,并且中途不会被打断,而且不设置回调函数的话,Promise 内部出错不会被抛出外部;再者如果它的状态如果是“处理中”,则无法知悉进行到了什么程度;
1、Promise 基本用法
Promise对象是一个构造函数,接收一个函数作为自己的参数,这个函数接受两个参数:resolve
和 reject
上实例:
let promise = new Promise( (resolve,reject) => {
//doing something
if (/*成功*/) {
resolve(value);
}
if (/*失败*/) {
reject(error);
}
});
Promise对象的实例生成之后,可以用then()
方法来指定“成功”和“失败”两种状态的回调函数。then()
方法还可以链式调用(因为then()
方法返回的是一个Promise对象),
promise.then(
(value)=>{
// 成功,doing something
}, (error)=>{
//失败,doing something
}).then((value)=>{}, (error)=>{}).then((value)=>{}, (error)=>{});
第二个回调函数是可选参数;
回调函数的参数必须要与Promise传出的值对应上;
这里来个例子,用 Promise 对象改写 Ajax 操作:
//因为ajax 输出是放在了 xhr.onreadystatechange 阶段,对应 resolve() reject()
let getText = function (url) {
let promise = new Promise((resolve, reject)=>{
//创建xhr 对象
let xhr = new XMLHttpRequest();
//打开连接
xhr.open('get', url);
//发送
xhr.send(null);
//接收
xhr.onreadystatechange = function () {
if (xhr.readystate == 4) {
if (xhr.status == 200 && xhr.status < 300 || xhr.status == 304) {
resolve(xhr.responseText);
//这一句不会不catch()捕抓到,因为Promise 对象的状态已经确定下来
throw new Error('test');
}else{
reject(xhr.status);
}
}
}
});
return promise;
};
那么来看看都还有哪些方法是值得学一学的:
Promise 实例除了有then()
方法,还有个catch()
方法::
catch()
方法接收一个函数作为参数,实际上是 then(null, fn)
的另一种形式。
- 我们再来看看
catch()
方法的一些特点:只要Promise 对象的状态确定下来之前,链式调用中排在它前面的所有错误它都能捕抓到。也就是说,状态确定下来再去捕抓错误是无效的,而Promise 对象的错误具有冒泡性质,会逐步往后传递直到被捕抓; - 如果没有写
catch()
方法,promise 对象抛出的错误不会传递到外面,也就是说虽然出错了,你完全不知道错在何处,什么错误,所以还是老老实实写上这个方法; - 另外还有个小建议,最好
then()
的第二个参数不传,直接上catch()
方法来代替;
getText().then((text)=>{
//doing something
}).catch((error)=>{
//getText 和 then()方法中的回调函数出现的错误会被捕抓到
//注意:getText 中 throw new Error('test');这一句catch()捕抓不到,
//因为Promise 对象的状态已经确定下来
console.log('Error:' error);
});
2、再来看看 Promise 对象的其他方法:
Promise.resolve()
这个方法用来将传入的参数,直接转化为Promise 对象;
- 可以不传参:这时候就会直接返回一个状态为“成功”的 Promise 对象,值得注意的是:这个
resolve()
方法本轮“事件循环”结束的时候执行,也就是说会比setTimeout()
先执行; - 参数是一个Promise 对象实例:直接返回这个实例;
- 参数是一个带有
then()
方法的 thenable 对象:这时候会直接将这个对象转化成Promise 对象,然后马上执行 thenable 对象的then()
方法;
let thenable = {
then: function (resolve, reject) {
resolve('hello world');
}
};
let promise = Promise.resolve(thenable);
promise.then((val)=>{
console.log(val); //hello world
});
- 参数是一个不带
then()
方法的对象或者根本就是个值:那么这个方法会直接返回一个状态为“成功”的 Promise对象实例,传入的参数也就是回调函数的参数。
let p = Promise.resolve('hello world');
p.then((string)=>{
console.log(string); //hello world
});
-
-
Promise.all()
这个方法用于将多个Promise 实例包装成一个新的Promise 实例,接收的参数是一个数组(其实只要有Iterator 接口并且返回的所有成员都是Promise 对象实例就行了):
注意:如果p1, p2, p3 还不是Promise 实例,就要手动用 Promise.resolve()
来处理一下;
let newP = Promise.all([p1, p2, p3]);
那么这个newP 实例的状态由什么决定呢?
由p1, p2, p3 三个实例的状态共同决定。
p1, p2, p3 全都为“成功”,则 newP 的状态为“成功”,三者的返回值就会以数组的形式传递给 newP 的回调函数;
p1, p2, p3 只要有一个是“失败”,则newP 的状态就“失败”,第一个状态为“失败”的实例的返回值就会传递给 newP 的回调函数;
Promise.race()
这个方法也是用于将多个Promise 实例包装成一个新的Promise 实例,接收的参数是一个数组
let newP = Promise.race([p1, p2, p3]);
那么返回的实例的状态由什么决定呢?
也是由p1、p2、p3 共同决定;
p1, p2, p3 中只要有一个实例的状态首先确定下来,那么newP 的状态也跟着确定下来,率先改变状态的实例的返回值会传递给 newP 的回调函数;
Promise.reject(reason)
这个方法会返回一个状态为“失败” 的 Promise 实例;
传入的参数会直接作为“失败”的返回值传递给后面的回调函数
let p = Promise.reject(reason);
//equal to
let p = new Promise((resolve,reject)=>{
reject(reason);
});
最后,大家如果觉得ES6 提供的API 不够用,那就直接自己造啊:
人有多大胆,地有多大产;
Promise.prototype.xxxx = function () {
};