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