Promise 的含义
Promise 是异步编程的一种解决方案,以传统的解决方案:回调函数和事件更合理和强大。
定义
Promise 是一个对象,从它可以回去一步操作的消息,Promise提供统一的API,各种异步操作都可以用同样的方法进行处理。
特点
- 对象的状态不受外界的影响,对象的状态有
pending(进行中)、fulfilled(成功)、rejected(已失败)。只有异步操作的结果可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。 - 一旦状态改变,就不会再变,任何时候都可以的到这个结果。
Promise对象的状态改变只有:1从pending变为fulfilled或者从pending变为rejected,只要这两个情况发生了,状态就不会再变了。
优点
- 有了Promise对象,就可以将一步操作以同步流程表达出来,避免了层层嵌套的回调函数。
- 提供统一的接口,是的控制异步操作更加容易
缺点
- 无法取消Promise
- 内部抛出的错误无法反映到外部
- 当处于
pending状态时,无法知道进行到哪一个阶段
基本用法
ES6中,Promise对象是一个构造函数,用来生成Promise实例
const promise = new Promise(function(resolve, reject){
// ...
// ... resolve(value)
// ... reject(error)
})
构造函数Promise接受一个参数,函数的参数分别是resolve和reject。这两个函数由JavaScript引起提供,不用自己部署
函数resolve作用将Promise对象的状态从未完成变为成功,操作成功时调用,并将一步操作的结果作为参数传递出去。
函数reject作用将Promise对象的状态从未完成变为失败,在异步操作失败时调用,并将异步操作的报错作为参数传递出去。
方法then可以接受两个回调函数作为参数,第一个回调函数的是Promise 对象的状态变为resolved时调用,第二个回调函数是Promise对象的状态变为rejected时调用。其中,第二个函数是可以选的。
function timeout(ms){
return new Promise((resolve,reject)=>{
setTimeout(resolve,ms,'done');
})
}
timeout(100).then((value)=>{
console.log(value); // 'done'
})
Promise 新建以后就会立即执行
const promise = new Promise((resolve, reject)=>{
console.log('Promise');
resolve();
});
promise.then(()=>{
console.log('resolve');
});
console.log('abc');
// 输出顺序
// 'Promise'
// 'abc'
// 'resolve'
当调用resolve函数和reject函数带有参数时,那么这些参数会被传递给回调函数。reject函数的参数通常是Error对象的实例,表示抛出异常错误;resolve函数的参数除了正常值以外,还可能是另一个Promis 实例。
const promise1 = new Promise(function(resolve,reject){
// ...
})
const promise2 = new Promise(function(resolve,reject){
// ...
resolve(p1);
})
上面的代码中,promise1和promise2都是 Promise 的实例,但是promise2的resolve方法将promise1作为参数,即:一个异步操作的结果是返回另一个异步操作。注意,这个时候的promise1的状态会传递给promise2,promise1的状态决定promise2的状态。当promise1的状态为pending那么promise2的回调函数会等待promise1的状态的改变。当promise1的状态为resolved或者为reject,那么promise2的回调函数会立即执行。
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('p1 is reject');
reject(new Error('fail'));
}, 3000);
});
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('p2 is resolve');
resolve(p1);
}, 1000);
});
p2.then(result => console.log(result))
.catch(error => console.log(error));
// p2 is resolve
// p1 is reject
// Error: fail
上面代码p1和p2在创建的时候都是立即执行的,在执行经过1000ms以后,p2输出p2 is resolve然后等待p1的状态,再经过2000ms以后,p1输出p1 is reject,然后p1的状态为reject并抛出异常,这个时候p2再次执行,由于p1抛出异常,所以被p2捕获,此时就输出Error: fail.
- 调用
resolve或者reject并不会终结Promis的参数函数的执行
const promise = new Promise((resolve, reject)=>{
resolve(1);
console.log(2);
}).then(value=>{
console.log(value);
});
// 2
// 1
Promise.prototype.then()
Promise实例具有定义在原型对象上的then方法,then方法为Promise实例添加状态改变时的回调函数。then方法的第一个参数是resolved状态的回调函数,第二个参数是rejected状态的回调函数。then方法返回一个新的Promise实例(和其之前不同的Promise实例)。因此可以采用链式写法。
- 采用链式写法,可以指定一组按照顺序调用的回调函数。
const promise = new Promise((resolve,reject) => {
// ...
}).then(r => {
// ...
}).then(end=>{
// ...
});
Promise.prototype.catch()
实例方法catch()是在.then(null,rejection)或者.then(undefined,rejection)发生错误的时候回调函数。
- 如果异步操作抛出错误,状态就会变为
rejected,就会调用catch方法指定的回调函数,处理这个错误。 - 方法
then指定的回调函数如果运行中抛出错误,也会被catch方法捕获. - 当Promise 状态已经变成
resolved再抛出错误是无效的 - Promise对象的错误具有"冒泡"性质,会一直向后传递,知道捕获为止。错误总会被下一个
catch语句捕获。 - 不在
then方法里面定义reject状态的回调函数(then的第二个参数),总是使用catch方法
// 不好的写法
promise.then(function(data){
// success
},function(error){
// error
});
// nice 的写法
promise.then(function(data){
// success
}).catch(function(error){
// error
});
catch和try...catch块的区别
如果没有使用cathch方法指定错误处理的回调函数,Promise对象抛出的错误不会传递到外层代码,使整个应用不会报错。
建议
Promise对象后面要跟着catch方法,这样可以处理Promise 内部发生的错误。catch方法返回的还是一个Promise对象,因此后面还是可以接着调用then方法的。
Promise.prototype.finally()
在***ES2018***,finally()方法用于指定不管Promise对象最后的状态如何,都会执行的操作。
const promise = new Promsie((resolve,reject) => {
// ...
}).then(() => {
// ...
}).catch(() => {
// ...
}).finally(() => {
// ...
})
finally方法里的操作,是与状态无关的,不依赖于Promise的执行结果。finally本质上是then方法的特例
const promise = new Promise((resolve,reject)=>{
// ...
});
promise.finally(()=>{
// ...
});
promise.then(result=>{
// ...
return result;
},error=>{
// ...
throw error;
})
finally方法的实现
Promise.prototype.finally = function (callback) {
let P = this.constructor;
return this.then(
value => P.resolve(callback()).then(() => value);
reason => P.resolve(callback()).then(() => {throw reason});
);
};
Promise.all()
方法Promise.all()用于将多个Promise实例包装成一个新的Promise实例
方法Promise.all()参数必须具有Iterator接口,并且返回的每一个成员都是Promise实例
// p1,p2,p3 都是Promise的实例
const p = Promise.all([p1, p2, p3]);
上面的代码中,p的状态由p1,p2,p3决定。
当p1,p2,p3的状态都是fulfilled,p的状态才会成为fulfilled,此时,p1,p2,p3的返回值组成一个数组,传递给p的回调函数。
当p1,p2,p3的状态有一个是rejected,p的状态就是rejected,此时,第一个被rejected的实例返回值,会传递给p的回调函数。
触发Promise.all()的catch方法的条件
如果作为参数的Promise实例已经定义了
catch方法,那个它被rejected以后,并不会触发Promise.all()的catch方法如果作为参数的Promise实例没有定义
catch方法,那个它被rejected以后,并会触发Promise.all()的catch方法,
Promise.race()
方法Promise.race()将多个Promise实例,包装成一个新的Promise实例。
// p1, p2, p3 都是 Promise 的实例
const promise = Promise.race([p1, p2, p3]);
在包装的Promise实例中,有一个实例率先改变状态,那么Promise.race()(上述代码中的promise)产生的实例的状态也跟着改变。
Promise.allSettled()
方法Promise.allSettled()接受一组Promise实例作为参数,包装成一个新的Promise实例,只有等到所有的参数实例都返回结果,不管结果是fulfilled还是rejected包装实例才会结束.
Promise.any()
方法Promise.any()接受一组Promise实例作为参数,包装成一个新的Promise实例,只要有一个参数实例状态是fulfilled包装实例就会变成fulfilled状态.如果所有参数实例都是reject状态,包装实例才会变成rejected状态。
Promise.resolve()
方法Promise.resolve()可以将现有对象转换为Promise对象
const jsPromise = Promise.resolve($.ajax('/whatever.json'));
上面的代码将jQuery生成的deferred对象,转为一个新的Promise对象
方法Promise.resolve()等价写法
Promise.resolve('foo');
// 等价于
new Promise(resolve => resolve('foo'));
参数
- Promise实例
thenable对象
具体的thenable对象指具有then方法的对象,Promise.resolve方法将这个对象转为Promise对象,然后立即执行thenable对象的then方法
- 参数不具有
then方法,或者根本不是对象
方法Promise.resolve方法返回一个新的Promise对象,状态为resolved.
- 不带任何参数
方法Promise.resolve()方法允许调用是不带参数,直接返回一个resolved状态的Promise对象
立即
resolve()的Promise对象,是在本轮“事件循环(event loop)”的结束执行,而不是在下一轮“事件循环”的开始时。
Promise.reject()
方法Promise.reject()返回一个新的Promise实例,这个实例的状态为rejected.
方法Promise.reject()的参数,会原封不动的作为reject的reason,作为后续方法的参数。
应用
- 图片加载
将图片加载写为一个Promise,一旦加载完成,Promise的状态就会发生改变
const preloadImage = function(path){
return new Promise(function(resolve,reject){
const image = new Image();
image.onload = resolve;
image.onerror = reject;
image.src = path;
});
};
- Generator 函数与Promise 的结合
Promise.try()
使同步函数同步执行,异步函数异步执行,不做特殊区分,使它们具有统一的API的方法
- 使用
async函数
// f() 是同步的
const f = () => console.log('abc');
(async () => f())();
console.log('next');
// 'abc'
// 'next'
// f() 是异步的
(async () => f())()
.then(...)
.catch(...)
- 使用
new Promise()
const f = () => console.log('abc');
(
()=> new Promise(
resolve => resolve(f())
)
)();
console.log('next');
此时的同步函数f()也是可以立即执行的。
而方法Promise.try()可以代替上面的方法
const f = () => console.log('abc');
Promise.try(f());
console.log('next');
方法Promise.try()就是模拟try代码块,就像Promise.catch模拟catch代码块
备注:本文是自己学习阮一峰老师的《ECMAScript 6 入门》所做的笔记,大部分例子来源于此书。