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 入门》所做的笔记,大部分例子来源于此书。