1.首先,promise是什么
promise是一种异步编程的解决方法。
2.为什么用promise
为了解决多个异步回调难以维护和控制的问题。
例如:多个定时器嵌套的做法
setTimeout(function () {
console.log(1)
setTimeout(function () {
console.log(2)
setTimeout(function () {
console.log(3)
}, 1000)
}, 1000)
}, 1000)
首先,这种做法一眼看上去,就没有组织性,不够简洁。如果,再往每个定时器里面写业务逻辑,那么,就显得很乱,没有章法,不好维护。这就是传统异步编程的缺点。
3.promise的基础用法
(1)promise对象有三种状态,分别为pending(进行中)、fulfilled(已成功)、rejected(已失败)。只有异步操作的结果能决定是哪种状态。
(2)Promise构造函数接受一个函数作为参数:
-
Promise构造函数接收一个函数作为参数,该函数的两个参数分别是resolve和reject,他们是两个函数,由Javascript引擎提供,不用自己部署。
-
接受的这个函数叫executor函数,即执行函数。它在Promise构造函数执行时,立即调用执行,它内部一般是一个异步操作。一旦异步操作执行完毕(可能成功/失败),要么调用resolve函数来将promise状态改成fulfilled,要么调用reject 函数将promise的状态改为rejected。
-
resolve函数的作用是,将promise对象的状态从“pending”变为‘’resolved‘’,在异步操作成功时调用,并将异步操作的结果,作为参数传递出去。这里需要注意下,resolve函数的参数除了正常的值以外,还可能是另一个 Promise 实例,比如像下面这样:
const p1 = new Promise(function (resolve, reject) {
// ...
});
const p2 = new Promise(function (resolve, reject) {
// ...
resolve(p1);
})
p1的状态决定了p2的状态。如果p1的状态是pending,那么p2的回调函数就会等待p1的状态改变;如果p1的状态已经是resolved或者rejected,那么p2的回调函数将会立刻执行。
- reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
即如下实例: - 注意:Promise 的状态一旦改变,就永久保持该状态,不会再变了。也就是,如果executor函数中还有能改变promise状态的语句,该语句不会被调用了,then方法中的回调函数也只会被执行一次。
var promise = new Promise( function( resolve, reject) {
//some code
if(//异步操作成功){
resolve(value); //将value传给成功的回调函数,即then方法的第一个参数函数
}else{
reject(error);
}
});
4.promise错误捕获
(1)promise对异步编程中的错误捕获处理,也更为合理。
- 虽然then方法的第二个参数可以用于捕获异步操作中的错误,但更建议用catch方法来捕获错误。
- 若then方法中写了Promise状态变为rejected的回调函数,则即使后面有catch也不会调用的。
- 如果没有指定错误处理的回调函数,Promise 对象抛出的错误不会传递到外层代码,即不会有任何反应。Promise 内部的错误不会影响到 Promise 外部的代码。
// bad
promise
.then(function(res) {
// success
}, function(rej) {
// error
});
// good
promise
.then(function(res) { //cb
// success
}, function(rej) { // 抛出的错误会被该函数捕获,下面的catch方法不执行。
})
.catch(function(err) {
// error
});
(2)catch方法可以捕获promise链中任何一环抛出的错误。并且,会返回新的Promise实例,后面有then方法的话,会继续执行的。
function fn() {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject('error')
}, 1000)
})
}
fn().then(() => {
console.log('第一个then'); // 不执行
}).then(() => {
console.log('第二个then'); // 不执行
}).catch((error) => {
console.log(error);
}).then(() => {
// 该函数在catch方法执行完后,接着执行。(因为他是新的Promise实例的方法)
})
(3)关于错误捕获注意点
- promise对象中的异步操作(一般是一个异步函数)结果失败时,用
reject()
函数抛出错误。
const promise = new Promise(function(resolve, reject) {
setTimeout(() => {// 模拟异步操作
try {
throw new Error('test');
} catch(e) {
reject(e);
}
}, 300)
});
promise.catch(function(error) {
console.log(error);
});
不能这样写:
(在异步函数中抛出的错误不会被捕获到)
const readTime = function (value, time = 500) {
return new Promise(function (resolve, reject) {
setTimeout(() => {
throw new Error('test');
}, 300)
});
};
readTime()
.then(res => {
console.log(res);
})
.catch(e => {
console.log('e===',e);
});
上述定时器中的错误,catch
是捕获不到的。
5.Promise.all
- Promise.all()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
const p = Promise.all([p1, p2, p3]);
上面代码中,Promise.all()方法接受一个数组作为参数,p1、p2、p3都是 Promise 实例,如果不是,就会先调用Promise.resolve()方法,将参数转为 Promise 实例,再进一步处理。另外,Promise.all()方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。
- p的状态由p1、p2、p3决定,分成两种情况。
(1)只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。数组中返回值的顺序,和传入的Promise实例的顺序一样。
(2)只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数(有两种可能,一是then方法中写了rej状态的回调,二是写了catch方法)。
const p1 = new Promise((resolve, reject) => {
resolve('p1--hello');
}).then(result => result)
const p2 = new Promise((resolve, reject) => {
reject('p2--hello');
}).then(result => { // 该函数不会执行的,因为promise实例的状态为rejected
return result
})
Promise.all([p1, p2])
.then(res => {
console.log('res所有promise都执行完==');
console.log(res)
}, rej => {
console.log('rej所有promise都执行完==');
console.log(rej) // 打印 'p2--hello'
})
.catch(e => { // 这里不会执行的,因为then方法中有rejected的回调了。
console.log('error所有promise都执行完==');
console.log(e)
});
- 等待
Promise.all()
方法接受的所有promise实例的状态全都改变了(不管是成功还是失败)之后,才会执行Promise.all
后面的回调函数。
const p1 = new Promise((resolve, reject) => {
console.log('p1执行==');
resolve('hello');
}).then(result => result);
const p2 = new Promise((resolve, reject) => {
console.log('p2执行==');
throw new Error('报错了');
}).then(result => {
console.log(result);
})
Promise.all([p1, p2])
.then(res => {
console.log('res所有promise都执行完==');
console.log(res)
})
.catch(e => { // 等p1,p2内部异步操作执行完后,执行该方法
console.log('error所有promise都执行完==');
console.log(e)
});
- 注意,如果作为参数的 Promise 实例,自己定义了catch方法,那么它一旦被rejected,并不会触发Promise.all()的catch方法。
const p1 = new Promise((resolve, reject) => {
resolve('hello');
})
.then(result => result)
.catch(e => e);
const p2 = new Promise((resolve, reject) => {
throw new Error('报错了');
})
.then(result => result)
.catch(e => e);
Promise.all([p1, p2])
.then(result => console.log(result))
.catch(e => console.log(e));
// ["hello", Error: 报错了]
上面代码中,p1会resolved,p2首先会rejected,但是p2有自己的catch方法,该方法返回的是一个新的 Promise 实例,p2指向的实际上是这个实例。该实例执行完catch方法后,会变成resolved,导致Promise.all()方法参数里面的两个实例都会resolved,因此会调用then方法指定的回调函数,而不会调用catch方法指定的回调函数。
- 当多个异步操作(一般是请求数据)没有继发关系的时候,Promise.all()可用于多个异步操作并发。