目录
异步编程的一种解决方案。
一、特点
1、对象的状态不受外界影响。
Promise
对象代表一个异步操作,有三种状态:pending
(进行中)、fulfilled
(已成功)和rejected
(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。
2、一旦状态改变,就不会再变,任何时候都可以得到这个结果。
Promise
对象的状态改变,只有两种可能:从pending
变为fulfilled
和从pending
变为rejected
。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。
3、缺点:
- 无法取消
Promise
,一旦新建它就会立即执行,无法中途取消。 - 如果不设置回调函数,
Promise
内部抛出的错误,不会反应到外部。 - 当处于
pending
状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
二、基本用法
1、创建
Promise
对象是一个构造函数,用来生成Promise
实例
const promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
Promise
构造函数接受一个函数作为参数,该函数的两个参数分别是resolve
和reject
。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。
resolve
函数的作用是,将Promise
对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject
函数的作用是,将Promise
对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
如果调用resolve
函数和reject
函数时带有参数,那么它们的参数会被传递给回调函数。reject
函数的参数通常是Error
对象的实例,表示抛出的错误;resolve
函数的参数除了正常的值以外,还可能是另一个 Promise 实例
const p1 = new Promise(function (resolve, reject) {
// ...
});
const p2 = new Promise(function (resolve, reject) {
// ...
resolve(p1);
})
p1和p2都是 Promise 的实例,但是p2的resolve方法将p1作为参数,即一个异步操作的结果是返回另一个异步操作
这时p1的状态就会传递给p2,p1的状态决定了p2的状态。
如果p1的状态是pending,那么p2的回调函数就会等待p1的状态改变
如果p1的状态已经是resolved或者rejected,那么p2的回调函数将会立刻执行
2、then指定状态的回调函数
Promise
实例生成以后,可以用then
方法分别指定resolved
状态和rejected
状态的回调函数
promise.then(function(value) {
// success
}, function(error) {
// failure
});
then
方法可以接受两个回调函数作为参数。
- 第一个回调函数是
Promise
对象的状态变为resolved
时调用, - 第二个回调函数是
Promise
对象的状态变为rejected
时调用。 - 这两个函数都是可选的,不一定要提供。它们都接受
Promise
对象传出的值作为参数。
3、执行顺序
Promise 新建后就会立即执行
then
方法指定的回调函数,将在当前脚本所有同步任务执行完才会执行,所以resolved
最后输出。
4、实例方法then
定义在原型对象Promise.prototype
上,作用是为 Promise 实例添加状态改变时的回调函数。前面说过,then
方法的第一个参数是resolved
状态的回调函数,第二个参数是rejected
状态的回调函数,它们都是可选的。
返回值:一个新的Promise
实例
链式写法:then
方法后面再调用另一个then
方法,前一个then的回调函数完成以后的结果传给第二个then的回调函数
5、实例方法catch
用于指定发生错误时的回调函数,是.then(null, rejection)
或.then(undefined, rejection)
的别名
getJSON('/posts.json').then(function(posts) {
// ...
}).catch(function(error) {
// 处理 getJSON 和 前一个回调函数运行时发生的错误
console.log('发生错误!', error);
});
getJSON()方法返回一个 Promise 对象
如果该对象状态变为resolved,则会调用then()方法指定的回调函数;
如果异步操作抛出错误,状态就会变为rejected,就会调用catch()方法指定的回调函数,处理这个错误
另外,then()方法指定的回调函数,如果运行中抛出错误,也会被catch()方法捕获
Promise 在resolve
语句后面,再抛出错误,不会被捕获,等于没有抛出。因为 Promise 的状态一旦改变,就永久保持该状态,不会再变了。Promise 对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个catch
语句捕获
建议!不要在then()
方法里面定义 Reject 状态的回调函数(即then
的第二个参数),总是使用catch
方法
跟传统的try/catch
代码块不同的是:
如果没有使用catch()
方法指定错误处理的回调函数,Promise 对象抛出的错误不会传递到外层代码,即不会有任何反应
6、实例方法finally
作用:指定不管 Promise 对象最后状态如何,都会执行的操作。
参数:回调函数,但回调函数不接受任何参数。=》说明:finally
方法里面的操作,应该是与状态无关的,不依赖于 Promise 的执行结果。
本质:finally
本质上是then
方法的特例
promise
.finally(() => {
// 语句
});
// 等同于
promise
.then(
result => {
// 语句
return result;
},
error => {
// 语句
throw error;
}
);
如果不使用finally方法,同样的语句需要为成功和失败两种情况各写一次。
有了finally方法,则只需要写一次。
不管promise
最后的状态,在执行完then
或catch
指定的回调函数以后,都会执行finally
方法指定的回调函数
promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});
finally
方法总是会返回原来的值
// resolve 的值是 undefined
Promise.resolve(2).then(() => {}, () => {})
// resolve 的值是 2
Promise.resolve(2).finally(() => {})
// reject 的值是 undefined
Promise.reject(3).then(() => {}, () => {})
// reject 的值是 3
Promise.reject(3).finally(() => {})
手写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 })
);
};
7、实例方法all()
作用:将多个 Promise 实例,包装成一个新的 Promise 实例
参数:一个数组(或者Promise.resolve
方法且将参数转为 Promise 实例的对象),数组里的元素都是promise实例。如果数组中存在不是promise实例的元素,先调用Promise.resolve
方法,将参数转为 Promise 实例
Promise.all()的状态的决定:.
- 数组里所有的promise实例的状态都是fullfilled,Promise.all()的状态就是fullfilled,此时数组里元素的返回值组成一个数组,传递给Promise.all()的回调函数
- 数组里的promise实例只要有一个的状态是rejected,Promise.all()的状态就会变成rejected,数组里第一个被rejected的promise实例的返回值传给Promise.all()的回调函数
如果作为参数的 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方法指定的回调函数。
--------------------------------------------------------------
当p2没有自己的catch方法,就会调用Promise.all()的catch方法
const p1 = new Promise((resolve, reject) => {
resolve('hello');
})
.then(result => result);
const p2 = new Promise((resolve, reject) => {
throw new Error('报错了');
})
.then(result => result);
Promise.all([p1, p2])
.then(result => console.log(result))
.catch(e => console.log(e));
// Error: 报错了
8、实例方法race
作用:将多个 Promise 实例,包装成一个新的 Promise 实例
参数:一个数组,数组里的元素都是promise实例。如果不是 Promise 实例,就会先调用Promise.resolve()
方法,将参数转为 Promise 实例
Promise.race()状态的决定:数组里的promise实例只要有一个实例率先改变状态,这个实例的返回值就传给Promise.race(),Promise.race()的状态就跟着改变,
9、实例方法allSettled()
作用:确定一组异步操作是否都结束了(不管成功或失败)。希望等到一组异步操作都结束了,不管每一个操作是成功还是失败,再进行下一步操作。
参数:一个数组,数组的每个成员都是一个 Promise 对象,并返回一个新的 Promise 对象。只有等到参数数组的所有 Promise 对象都发生状态变更(不管是fulfilled
还是rejected
),返回的 Promise 对象才会发生状态变更。
返回值:一个新的promise实例。状态发生改变后状态总是fulfilled
const resolved = Promise.resolve(42);
const rejected = Promise.reject(-1);
const allSettledPromise = Promise.allSettled([resolved, rejected]);
allSettledPromise.then(function (results) {
console.log(results);
});
// [
// { status: 'fulfilled', value: 42 },
// { status: 'rejected', reason: -1 }
// ]
Promise.allSettled()的返回值allSettledPromise,状态只可能变成fulfilled。(对应上面返回值的知识点)
它的回调函数接收到的参数是数组results。该数组的每个成员都是一个对象,对应传入Promise.allSettled()的数组里面的两个 Promise 对象。
results的每个成员是一个对象,对象的格式是固定的,对应异步操作的结果
参数数组中的成员对象的status
属性的值只可能是字符串fulfilled
或字符串rejected
,用来区分异步操作是成功还是失败。如果是成功(fulfilled
),对象会有value
属性,如果是失败(rejected
),会有reason
属性,对应两种状态时前面异步操作的返回值
const promises = [ fetch('index.html'), fetch('https://does-not-exist/') ];
const results = await Promise.allSettled(promises);
// 过滤出成功的请求
const successfulPromises = results.filter(p => p.status === 'fulfilled');
// 过滤出失败的请求,并输出原因
const errors = results
.filter(p => p.status === 'rejected')
.map(p => p.reason);
10、实例方法any
作用:只要参数实例有一个变成fulfilled
状态,包装实例就会变成fulfilled
状态;如果所有参数实例都变成rejected
状态,包装实例就会变成rejected
状态。
返回值:包装成一个新的 Promise 实例返回
参数:一组 Promise 实例
与Promise.race()的区别:Promise.any()
不会因为某个 Promise 变成rejected
状态而结束,必须等到所有参数 Promise 变成rejected
状态才会结束
11、实例方法resolve()
作用:将现有对象转为 Promise 对象
参数:
- 参数是 Promise 实例,那么
Promise.resolve
将不做任何修改、原封不动地返回这个实例 - 参数是一个
thenable
对象(具有then
方法的对象),Promise.resolve()
方法会将这个对象转为 Promise 对象,然后就立即执行thenable
对象的then()
方法 - 如果参数是一个原始值,或者是一个不具有
then()
方法的对象,则Promise.resolve()
方法返回一个新的 Promise 对象,状态为resolved
Promise.resolve()
方法允许调用时不带参数,直接返回一个resolved
状态的 Promise 对象
12、实例方法reject()
返回值:一个新的promise实例,该实例的状态为rejected
const p = Promise.reject('出错了');
// 等同于
const p = new Promise((resolve, reject) => reject('出错了'))
Promise.reject()
方法的参数,会原封不动地作为reject
的理由,变成后续方法的参数
Promise.reject('出错了')
.catch(e => {
console.log(e === '出错了')
})
// true
13、实例方法try
用法:不知道或者不想区分,函数f
是同步函数还是异步操作,但是想用 Promise 来处理它。因为这样就可以不管f
是否包含异步操作,都用then
方法指定下一步流程,用catch
方法处理f
抛出的错误
三、应用
1、promise包装图片异步加载
function loadImageAsync(url) {
return new Promise(function(resolve, reject) {
const image = new Image();
image.onload = function() {
resolve(image);
};
image.onerror = function() {
reject(new Error('Could not load image at ' + url));
};
image.src = url;
});
}
2、Generator 函数与 Promise 的结合
使用 Generator 函数管理流程,遇到异步操作的时候,通常返回一个Promise
对象
function getFoo () {
return new Promise(function (resolve, reject){
resolve('foo');
});
}
const g = function* () {
try {
const foo = yield getFoo();
console.log(foo);
} catch (e) {
console.log(e);
}
};
function run (generator) {
const it = generator();
function go(result) {
if (result.done) return result.value;
return result.value.then(function (value) {
return go(it.next(value));
}, function (error) {
return go(it.throw(error));
});
}
go(it.next());
}
run(g);