Promise对象
所谓Promise,简单来说是一个容器,里面保存着未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise是一个对象,从它可以获取异步操作的消息。
1. Promise对象的特点
- 对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中),fufilled(已成功)和rejected(已失败)。只有异步操作的结果可以决定当前是哪一种状态。
- 一旦状态改变,就不会再变。任何时候得到的结果都是该状态。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
- Promise实例新建后就会立即执行
- Promise对象中的异步操作会在所有同步操作执行完毕后执行,由于then方法的执行是在异步操作结束后执行,故该方法的执行也是在所有同步操作执行完毕后执行
//2例子
const promise = new Promise(function(resolve, reject) {
resolve('ok');
throw new Error('test');//由于抛出错误在resolve语句后,故不能被捕获
});
promise
.then(function(value) { console.log(value) })
.catch(function(error) { console.log(error) });
// 输出ok
//Promise实例的执行顺序
let data=null;
let promise=new Promise(function (resolve,reject){
console.log("start");
setTimeout(function () {
console.log("异步操作中");
resolve("数据")
},0)
});
console.log("外部");
promise.then(function (res){
data=res;
console.log("异步操作结束的data:"+data)
});
console.log("end");
console.log("异步操作未结束的data:"+data);
//start
//外部
//end
//异步操作未结束的data:null
//异步操作中
//异步操作结束的data:数据
2. Promise对象的优点:
- Promise对象可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。
- Promise对象提供的方法使得异步操作更加容易。
3. Promise对象的缺点
- 无法取消Promise,一旦建立Promise对象它就会立即自动执行,无法中途取消。
- 如果不设置回调函数,Promise内部抛出的错误不会反应到外部。
- 当处于pending状态时无法得知当前是刚开始状态还是即将完成状态。
4. 基本用法
① 由于Promise对象是一个构造函数,故可以通过new Promise(fn)
生成Promise实例。
② 回调函数fn的参数resolve函数的作用是将Promise对象的状态从未完成完成成功,在异步操作成功时调用,并将异步操作的结果作为参数传递出去。reject函数的作用是将Promise对象的状态从未完成状态变为失败状态,在异步操作失败时调用,并将异步操作报出的错误作为参数传递出去。
③ 注意:当resolve/reject的参数仍然是一个异步操作时:eg:p2的resolve方法将p1作为参数。此时p1的状态就会传递给p2,也就是说p1的状态决定了p2的状态。如果p1的状态是pending,那么p2的回调函数就会等待p1的状态改变,如果p1的状态是resolve/rejected,那么p2的回调函数会立刻执行。
④ 在③中,如果p1的状态是rejected,而p1是作为p2的resolve方法的参数,此时p2仍会执行rejected的回调函数,而不是执行resolve方法。
function timeout(ms) {
return new Promise((resolve,reject)=>{
setTimeout(()=>{
console.log("我在执行异步操作");
resolve("执行完毕")//resolve方法将异步操作结果传递出去
},ms)
})
}
timeout(1000).then((value)=>{
//在resolve的回调函数中拿到异步操作结果
setTimeout(()=>{
console.log(value)
},1000)
});
//异步实现Ajax
const getJSON = function(url) {
const promise = new Promise(function(resolve, reject){
const handler = function() {
if (this.readyState !== 4) {
return;
}
if (this.status === 200) {
//当异步操作(发请求)执行完毕后将拿到的返回值传递出去
resolve(this.response);
} else {
reject(new Error(this.statusText));//当异步操作(发请求)执行完毕后将拿到的错误信息传递出去
}
};
const client = new XMLHttpRequest();
client.open("GET", url);
client.onreadystatechange = handler;
client.responseType = "json";
client.setRequestHeader("Accept", "application/json");
client.send();
});
return promise;
};
//在.then方法中拿到传递出来的值
getJSON("/posts.json").then(function(json) {
console.log('Contents: ' + json);
}, function(error) {
console.error('出错了', error);
});
//在该例中,虽然过了一秒后p2的状态需要改变为resolve了,但是由于其实参为p1(p1为一个Promise实例,故需要等待p1状态改变。三秒后p1状态变为reject,此时p2需要去执行reject的回调函数)
const p1 = new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error('fail')), 3000)
})
const p2 = new Promise(function (resolve, reject) {
setTimeout(() => resolve(p1), 1000)
})
p2
.then(result => console.log(result))
.catch(error => console.log(error))
// Error: fail
5. Promise.prototype.then()
- Promise实例具有then方法,它的作用是为Promise实例添加状态改变时的回调函数。该方法返回的是一个新的Promise实例,故可以采用链式写法,即then方法后面再调用另一个then方法。
- 当用链式写法时,无论在resolve回调函数中
return 内容
还是在rejected回调函数中return 内容
都会赋值给Promise实例的promiseValue
,只有当Promise实例的promiseValue
不为undefined
时,才能在执行下一个then方法时的回调函数中取到值。- 无论在回调函数中return的为单纯的内容还是return的Promise实例对象,都可在下次.then的回调函数中取到值。两者的区别是:若return 的为Promise实例对象,则执行下一个then方法时执行哪个回调函数与返回的Promise实例对象的状态有关。
let p3=new Promise(function (resolve, reject) {
setTimeout(reject, 1000, "错误信息");
}).then(function (res){
return res;
}).catch((value)=>{
return value;//将值赋值给Promise实例对象的promiseValue属性
}).then(function (value) {
return value;//只要promiseValue属性有值便可以获取到
}).catch(function (reason) {
console.log(reason);
})
console.log(p3)
上面例子中打印出来的p3
6. Promise.prototype.catch()
- 该方法用于指定发生错误时的回调函数。
- Promise对象的错误具有"冒泡"性质。会一直向后传递,直到被捕获位置。也就是说错误总会被下一个catch语句捕获。
- 一般来说,最好使用catch方法,而不是给then方法添加第二个参数。因为使用catch方法可以捕获到then方法执行的错误
- 如果没有使用catch方法指定错误处理的回调函数,Promise抛出的错误不会传递到外层代码,即不会影响外部其他代码的执行。
//下面三个方法等价
//①
const promise = new Promise(function(resolve, reject) {
throw new Error('test');
});
promise.catch(function(error) {
console.log(error);
});
// Error: test
//②
const promise = new Promise(function(resolve, reject) {
try {
throw new Error('test');
} catch(e) {
reject(e);
}
});
promise.catch(function(error) {
console.log(error);
});
//③
const promise = new Promise(function(resolve, reject) {
reject(new Error('test'));
});
promise.catch(function(error) {
console.log(error);
});
//下面代码中,一共有三个Promise对象,一个由getJSON产生,两个由then产生,三者之一任意一个抛出错误,都会被最后一个catch捕获
getJSON('/post/1.json').then(function(post) {
return getJSON(post.commentURL);
}).then(function(comments) {
// some code
}).catch(function(error) {
// 处理前面三个Promise产生的错误
});
//注意事项4举例
const someAsyncThing = function() {
return new Promise(function(resolve, reject) {
// 下面一行会报错,因为x没有声明
resolve(x + 2);
});
};
someAsyncThing().then(function() {//不会执行
console.log('everything is great');
});
setTimeout(() => { console.log(123) }, 2000);//仍会输出123,Promise实例内部抛出的错误不会影响外部代码的执行
// Uncaught (in promise) ReferenceError: x is not defined
// 123
7. Promise.prototype.finally()
- finally方法用于指定不管Promise对象最后状态如何,都会执行的操作。
- finally方法的回调函数不接受任何参数。
8. Promise.prototype.all()
- Promise.all()方法用于将多个Promise实例包装成一个新的Promise实例。
Promise.all()
方法接受一个数组作为参数,所有参数都是Promise的实例,如果不是,就会先调用Promise.resolve方法将参数转成Promise实例,再进一步处理。该方法的参数可以不是数组但必须具有遍历器接口,且返回的每个成员都为Promise实例。const p = Promise.all([p1,p2,p3])
。在该例子中,只有p1,p2,p3的状态都变为fufilled,p的状态才会编程fufilled.此时p1,p2,p3的返回值组成一个数组,传递给p的回调函数。只要p1,p2,p3之中任意一个被rejected,p的状态就变成rejected。此时第一个被reject的实例的返回值会传递给p的回调函数
const p1 = new Promise(function (resolve, reject) {
setTimeout(resolve, 1000, "学生");
});
const p2 = new Promise(function (resolve, reject) {
setTimeout(resolve, 1000, "老师");
});
Promise.all([p1,p2]).then((res)=>{
console.log(res);//["学生","老师"]
}).catch(function(err){
console.log(err);//如果p1和p2任意一个状态为rejected,则打印出第一个状态变为rejected的对象内容。
})
9. Promise.race()
Promise.race()
方法同样是将多个Promise实例包装成一个新的Promise实例- 只要参数(即多个Promise实例)中有任意一个实例率先改变状态,该方法返回的Promise实例的状态就会改变,且率先改变状态的实例的返回值会传递给该方法返回的Promise实例的回调函数。
- 如果该方法的参数不是Promise实例则会先调用Promise.resolve()方法,将参数先转成Promise实例再进行处理。