特点
1、对象的状态不受外界影响。
有三种状态:pending(进行中)
、fulfilled(已成功)
和rejected(已失败)
只有异步操作
的结果,可以决定
当前是哪一种状态
,任何其他操作都无法改变这个状态。
2、一旦状态改变,就不会再变
,任何时候都可以得到这个结果。
状态改变,只有两种可能:从pending
变为fulfilled
和从pending
变为rejected
Promise缺点
1、无法取消Promise
,一旦新建
它就会立即执行
,无法中途取消
2、如果不设置回调函数,Promise
内部抛出的错误,不会反应到外部
3、当处于pending
状态时,无法得知目前进展到哪一个阶段
只要fulfilled
、rejected
这两种情况发生,状态就凝固了
,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)
。
如果改变已经发生了,再对Promise
对象添加回调函数,也会立即得到这个结果
。
事件的特点是:
如果你错过了它,再去监听,是得不到结果
的
Promise
与事件(Event)
完全不同
基本用法
Promise
是一个构造函数
1、const promise = new Promise(function(resolve, reject) {});
2、new Promise
返回一个 promise对象
, 接收一个excutor函数
作为参数
3、excutor
有两个函数类型
形参resolve(成功) reject(失败)
,第二个函数是可选的,不一定要提供
const promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
复制代码
4、Promise
实例生成以后,可以用then
方法分别指定resolved
状态和rejected
状态的回调函数
。
var _promise = new Promise(function(resolve, reject){
setTimeout(function(){
var rand = Math.random();
if(rand<0.5){
resolve("resolve" + rand);
}else{
reject("reject" + rand);
}
},1000);
});
_promise.then(result => { // onFulfilled
console.log(result); // 'fulfilled'
}).catch(reason => { // onRejected 不会被调用
console.log(reason)
})
复制代码
5、Promise 新建后就会立即执行
Promise
新建后立即执行,所以首先输出的是“Promise”
。
then
方法指定的回调函数,将在当前脚本所有同步任务执行完才会执行
,所以resolved
最后输出
let promise = new Promise(function(resolve, reject) {
console.log('Promise');
resolve();
});
promise.then(function() {
console.log('resolved.');
});
console.log('Hi!');
// Promise
// Hi!
// resolved
复制代码
异步加载图片
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;
});
}
复制代码
用Promise对象实现的 Ajax 操作
const getJSON = function(url) {
const promise = new Promise(function(resolve, reject){
const handler = function() {
if (this.readyState !== 4) {
return;
}
if (this.status === 200) {
resolve(this.response);
} else {
reject(new Error(this.statusText));
}
};
const client = new XMLHttpRequest();
client.open("GET", url);
client.onreadystatechange = handler;
client.responseType = "json";
client.setRequestHeader("Accept", "application/json");
client.send();
});
return promise;
};
getJSON("/posts.json").then(function(json) {
console.log('Contents: ' + json);
}, function(error) {
console.error('出错了', error);
});
复制代码
resolve函数的参数除了正常的值以外,还可能是另一个 Promise 实例
一个异步操作的结果是返回另一个异步操作
const p1 = new Promise(function (resolve, reject) {
console.log(3)
setTimeout(() => reject(new Error('fail')), 3000)
})
const p2 = new Promise(function (resolve, reject) {
console.log(1)
setTimeout(() => resolve(p1), 1000)
})
p2.then(result => console.log(result,2))
.catch(error => console.log(error,4))
//3 //promise 创建就执行
//1 //promise 创建就执行
// Error: fail 4 //不执行2执行4 是因为 p2的状态(执行哪个回调)由p1决定
复制代码
p2 resolve 一个p1,p2的状态由p1的状态决定
上面代码p1
的状态就会传递给p2
,也就是说,p1的状态决定了p2的状态
。如果p1
的状态是pending
,那么p2
的回调函数就会等待p1
的状态改变;如果p1
的状态已经是resolved
或者rejected
,那么p2
的回调函数将会立刻执行。
p2 resolve 一个p1,导致p2
自己的状态无效了,由p1
的状态决定p2
的状态,后面的then
语句都变成针对后者(p1
)
p1
是一个 Promise
,3 秒之后变为rejected
。p2
的状态在 1 秒之后改变,resolve
方法返回的是p1
。由于p2
返回的(resolve)是另一个 Promise
,导致p2
自己的状态无效了,由p1
的状态决定p2
的状态。所以,后面的then
语句都变成针对后者(p1
)。又过了 2 秒,p1
变为rejected
,导致触发catch
方法指定的回调函数。
调用resolve或reject并不会终结 Promise 的参数函数的执行
一般来说,调用
resolve
或reject
以后,Promise
的使命就完成了,后继操作应该放到then
方法里面,而不应该直接写在resolve或reject
的后面。所以,最好在它们前面加上return
语句,这样就不会有意外。
//虽然能执行但是 没有任何作用
new Promise((resolve, reject) => {
resolve(1);
console.log(2);
}).then(r => {
console.log(r);
});
// 2
// 1
//正常推荐写法
new Promise((resolve, reject) => {
return resolve(1);
// 后面的语句不会执行
console.log(2);
})
复制代码
promise的三种状态
- pending (进行中)
- fulfilled(已成功)
- rejected(已失败)
1.promise 对象初始化状态为 pending
2.当调用resolve(成功),会由pending => fulfilled
3.当调用reject(失败),会由pending => rejected
pending => fulfilled/rejected, 一旦修改就不能再变
promise对象方法
Promise.prototype.then(resolved,rejected)方法
为
Promise
实例添加状态改变时的回调函数(resolve
(成功)/reject
(失败)的回调函数)
第一个参数是resolved
状态的回调函数
第二个参数(可选
)是rejected
状态的回调函数
返回值:then
方法返回的是一个新的Promise
实例(注意,不是原来那个Promise
实例)
// onFulfilled 是用来接收promise成功的值
// onRejected 是用来接收promise失败的原因
promise.then(onFulfilled, onRejected);
复制代码
resolve(成功) onFulfilled会被调用
const promise = new Promise((resolve, reject) => {
resolve('fulfilled'); // 状态由 pending => fulfilled
});
promise.then(result => { // onFulfilled
console.log(result); // 'fulfilled'
}, reason => { // onRejected 不会被调用
})
复制代码
reject(失败) onRejected会被调用
const promise = new Promise((resolve, reject) => {
reject('rejected'); // 状态由 pending => rejected
});
promise.then(result => { // onFulfilled 不会被调用
}, reason => { // onRejected
console.log(reason); // 'rejected'
})
复制代码
Promise.prototype.catch() ,用于指定发生错误时的回调函数
在链式写法中可以捕获前面then中发送的异常
catch
方法返回
的还是一个 Promise 对象
,因此后面还可以接着调用then
方法
如果
catch
中没有错误,就是resolve
的Promise
对象
promise.catch(onRejected)
相当于promise.then(null, onRrejected)
或promise.then(undefined, rejection)
的别名
catch能捕获的错误:
– 1、异步操作reject抛出错误
– 2、then方法指定的回调函数,运行中抛出错误(代码语法错误)
注意:
then函数的第二个参数onRejected
不能捕获当前onFulfilled中的异常(代码语法错误)
一般来说,不要在
then
方法里面定义Reject
状态的回调函数(即then的第二个参数),建议使用catch
方法。Promise 对象后面一定要跟catch方法,这样可以处理 Promise 内部发生的错误。
promise.then(onFulfilled, onRrejected);
//可以写为
promise.then(function(posts) {
// ...
}).catch(function(error) {
// 处理 getJSON 和 前一个回调函数运行时发生的错误
console.log('发生错误!', error);
});
复制代码
Promise 对象的错误具有“冒泡”性质(总是会被下一个catch
语句捕获)
Promise
对象的错误具有“冒泡”
性质,会一直向后传递
,直到被捕获为止。也就是说,错误总是会被下一个catch
语句捕获。
getJSON('/post/1.json').then(function(post) {
return getJSON(post.commentURL);
}).then(function(comments) {
// some code
}).catch(function(error) {
// 处理前面三个Promise产生的错误
});
复制代码
一共有三个Promise
对象:一个由getJSON
产生,两个由then
产生。它们之中任何一个抛出的错误,都会被最后一个catch
捕获。
Promise 内部的错误不会影响到 Promise 外部的代码
const someAsyncThing = function() {
return new Promise(function(resolve, reject) {
// 下面一行会报错,因为x没有声明
resolve(x + 2);
});
};
someAsyncThing().then(function() {
console.log('everything is great');
});
setTimeout(() => { console.log(123) }, 2000);
// Uncaught (in promise) ReferenceError: x is not defined
// 123
复制代码
Promise
对象,内部有语法错误。浏览器运行到这一行,会打印出错误提示ReferenceError: x is not defined
,但是不会退出进程、终止脚本执行
,2 秒之后还是会输出123
。
Promise.catch后的链式调用
const someAsyncThing = function() {
return new Promise(function(resolve, reject) {
// 下面一行会报错,因为x没有声明
resolve(x + 2);
});
};
someAsyncThing()
.catch(function(error) {
console.log('oh no', error);
})
.then(function() {
console.log('carry on');
});
// oh no [ReferenceError: x is not defined]
// carry on
复制代码
如果没有报错,则会跳过catch方法,直接执行后面的then方法
Promise.resolve()
.catch(function(error) {
console.log('oh no', error);
})
.then(function() {
console.log('carry on');
});
// carry on
复制代码
catch方法抛出一个错误会被下一个catch捕获 ,后面没有catch错误不会被捕获,也不会传递到外层
someAsyncThing().then(function() {
return someOtherAsyncThing();
}).catch(function(error) {
console.log('oh no', error);
// 下面一行会报错,因为y没有声明
y + 2;
}).catch(function(error) {
console.log('carry on', error);
});
// oh no [ReferenceError: x is not defined]
// carry on [ReferenceError: y is not defined]
复制代码
Promise.prototype.finally(),用于指定不管Promise
对象最后状态如何,都会执行的操作
用于指定不管
Promise
对象最后状态如何,都会执行的操作。该方法是ES2018
引入标准的。不管
promise
最后的状态,在执行完then
或catch
指定的回调函数以后,都会执行finally
方法指定的回调函数。
finally
方法的回调函数不接受任何参数
,这意味着没有办法知道,前面的Promise
状态到底是fulfilled
还是rejected
。这表明,finally
方法里面的操作,应该是与状态无关的,不依赖于Promise
的执行结果。
promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});
复制代码
服务器使用 Promise 处理请求,然后使用finally方法关掉服务器。
server.listen(port)
.then(function () {
// ...
})
.finally(server.stop);
复制代码
Promise 链式调用
promise.then
方法每次调用 都返回一个新的promise实例
所以可以链式写法
function taskA() {
console.log("Task A");
}
function taskB() {
console.log("Task B");
}
function onRejected(error) {
console.log("Catch Error: A or B", error);
}
var promise = Promise.resolve();
promise
.then(taskA)
.then(taskB)
.catch(onRejected) // 捕获前面then方法中的异常
复制代码
1、链式调用 依次传递结果(参数)
前一个回调函数完成以后,会将返回结果作为参数,传入下一个回调函数。
const p = new Promise(function (resolve, reject) {
setTimeout(() => resolve('aa'), 1000)
})
p.then((e) => {
console.log(e) //aa
return e;
}).then((e2) => {
console.log(e) //aa
//如果前面的then没有 return e ,e2就是undefined
})
复制代码
2、前一个回调函数,返回的还是一个Promise对象(即有异步操作)
后一个回调函数,会等待该Promise
对象的状态发生变化,才会被调用
getJSON("/post/1.json").then(function(post) {
return getJSON(post.commentURL);
}).then(function funcA(comments) {
console.log("resolved: ", comments);
}, function funcB(err){
console.log("rejected: ", err);
});
复制代码
3、使用箭头函数
getJSON("/post/1.json").then(
post => getJSON(post.commentURL)
).then(
comments => console.log("resolved: ", comments),
err => console.log("rejected: ", err)
);
复制代码
Promise的静态方法
Promise.resolve 将现有对象转为 Promise 对象
返回一个fulfilled状态的promise对象
Promise.resolve('hello').then(function(value){
console.log(value);
});
//hello
Promise.resolve('hello');
// 相当于
new Promise(resolve => {
resolve('hello');
});
复制代码
Promise.resolve方法的参数分成四种情况
(1)参数是一个 Promise 实例,那么Promise.resolve
将不做任何修改
、原封不动地返回这个实例
如果参数是
Promise
实例,那么Promise.resolve
将不做任何修改
、原封不动地返回这个实例。
(2)参数是一个thenable对象(具有then
方法的对象),Promise.resolve
方法会将这个对象转为 Promise
对象,然后就立即执行thenable
对象的then
方法
thenable
对象指的是具有then
方法的对象,比如下面这个对象。
let thenable = {
then: function(resolve, reject) {
resolve(42);
}
};
复制代码
Promise.resolve
方法会将这个对象转为 Promise
对象,然后就立即执行thenable
对象的then
方法。
let thenable = {
then: function(resolve, reject) {
resolve(42);
}
};
let p1 = Promise.resolve(thenable);
p1.then(function(value) {
console.log(value); // 42
});
复制代码
(3)参数不是具有then方法的对象,或根本就不是对象,返回一个新的 Promise
对象,状态为resolved
如果参数是一个原始值,或者是一个不具有
then
方法的对象,则Promise.resolve
方法返回一个新的Promise
对象,状态为resolved
。
const p = Promise.resolve('Hello');
p.then(function (s){
console.log(s) // Hello
}).catch(function (s){
console.log(s) //p状态为`resolved` 不执行
});
复制代码
(4)不带有任何参数,直接返回一个resolved状态的 Promise 对象
直接返回一个resolved状态的 Promise 对象。
const p = Promise.resolve();
p.then(function () {
// ...
});
复制代码
立即resolve()
的Promise
对象,是在本轮“事件循环”
(event loop)的结束时执行,而不是在下一轮
“事件循环”的开始时
setTimeout(function () {
console.log('three');
}, 0);
Promise.resolve().then(function () {
console.log('two');
});
console.log('one');
// one
// two
// three
复制代码
上面代码中:
setTimeout(fn, 0)在下一轮“事件循环”开始时执行
Promise.resolve()在本轮“事件循环”结束时执行
console.log(‘one’)则是立即执行,因此最先输出。
Promise.reject 返回一个rejected状态的promise对象
const p = Promise.reject('出错了');
// 等同于
const p = new Promise((resolve, reject) => reject('出错了'))
p.then(null, function (s) {
console.log(s)
});
// 出错了
复制代码
Promise.reject()方法的参数,会原封不动地作为reject的理由,变成后续方法的参数。这一点与Promise.resolve方法不一致。
const thenable = {
then(resolve, reject) {
reject('出错了');
}
};
Promise.reject(thenable)
.catch(e => {
console.log(e === thenable)
})
// true
复制代码
Promise.all 接收一个promise对象数组为参数
用于将多个 Promise 实例,包装成一个新的 Promise 实例
Promise.all
方法接受一个数组作为参数,p1、p2、p3
都是Promise
实例
如果不是
,就会先调用讲到的Promise.resolve
方法(返回一个fulfilled状态的promise对象),将参数转为 Promise 实例,再进一步处理。
Promise.all
方法的参数可以不是数组,但必须具有Iterator
接口,且返回的每个成员都是 Promise 实例。
p的状态由p1、p2、p3决定,分成两种情况:
(1)只有p1、p2、p3的状态都变成fulfilled
,p的状态才会变成fulfilled,此时p1、p2、p3
的返回值组成一个数组,传递给p的回调函数
(2)只要p1、p2、p3
之中有一个被rejected
,p
的状态就变成rejected
,此时第一个被reject
的实例的返回值,会传递给p
的回调函数
const p1 = new Promise((resolve, reject) => {
resolve(1);
});
const p2 = new Promise((resolve, reject) => {
resolve(2);
});
const p3 = new Promise((resolve, reject) => {
reject(3);
});
Promise.all([p1, p2, p3]).then(function(data){
console.log(data); // [1, 2, 3] 结果顺序和promise实例数组顺序是一致的
}).catch(function(err) {
console.log(err);
});
复制代码
下面代码中,
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)
.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)) // ["hello", Error: 报错了]
.catch(e => console.log(e));
复制代码
Promise.race 接收一个promise对象数组为参数,只要有一个调用完成 ,后面的调用不执行,直接调用Promise.race
function timerPromisefy(delay) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
resolve(delay);
}, delay);
});
}
var startDate = Date.now();
Promise.race([
timerPromisefy(10),
timerPromisefy(20),
timerPromisefy(30)
]).then(function (values) {
console.log(values); // 10
});
复制代码
Promise.allSettled()接受一组 Promise 实例作为参数,等到所有这些参数实例都返回结果才会结束
方法接受一组
Promise
实例作为参数,包装成一个新的Promise
实例。只有等到所有这些参数实例都返回结果,不管是fulfilled
还是rejected
,包装实例才会结束。该方法由ES2020
引入。该方法返回的新的
Promise
实例,一旦结束,状态总是fulfilled
,不会变成rejected
。状态变成fulfilled
后,Promise
的监听函数接收到的参数是一个数组,每个成员对应一个传入Promise.allSettled()
的Promise
实例。每个对象都有
status
属性,该属性的值只可能是字符串fulfilled
或字符串rejected
。
fulfilled
时,对象有value
属性,
rejected
时有reason
属性,对应两种状态的返回值。
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.any()
:只要有一个fulfilled
状态,包装实例就会变成fulfilled
状态,全部rejected
包装实例才会变成rejected
状态,跟Promise.race()
很像
该方法接受一组
Promise
实例作为参数,包装成一个新的Promise
实例返回。只要参数实例有一个变成fulfilled
状态,包装实例就会变成fulfilled
状态;如果所有参数实例都变成rejected
状态,包装实例就会变成rejected
状态
Promise.any()
跟Promise.race()
方法很像,只有一点不同,就是不会因为某个Promise
变成rejected
状态而结束。
const promises = [
fetch('/endpoint-a').then(() => 'a'),
fetch('/endpoint-b').then(() => 'b'),
fetch('/endpoint-c').then(() => 'c'),
];
try {
const first = await Promise.any(promises);
console.log(first);
} catch (error) {
console.log(error);
}
复制代码
上面代码中,Promise.any()
方法的参数数组包含三个 Promise
操作。其中只要有一个变成fulfilled
,Promise.any()
返回的 Promise
对象就变成fulfilled
。如果所有三个操作都变成rejected
,那么await
命令就会抛出错误。
捕捉错误时,如果不用try...catch
结构和 await
命令,可以像下面这样写:
Promise.any(promises).then(
(first) => {
// Any of the promises was fulfilled.
},
(error) => {
// All of the promises were rejected.
}
);
复制代码
例子
var resolved = Promise.resolve(42);
var rejected = Promise.reject(-1);
var alsoRejected = Promise.reject(Infinity);
Promise.any([resolved, rejected, alsoRejected]).then(function (result) {
console.log(result); // 42
});
Promise.any([rejected, alsoRejected]).catch(function (results) {
console.log(results); // [-1, Infinity]
});
复制代码
Promise.try(),不管f是否包含异步操作,都用then方法指定下一步流程
实际开发中,经常遇到一种情况:
不知道或者不想区分,函数
f
是同步函数还是异步操作,但是都想用Promise
来处理它。
Promise.try
就是模拟try
代码块,就像promise.catch
模拟的是catch
代码块。因为这样就可以
不管f是否包含异步操作
,都用then
方法指定下一步流程,用catch方法处理f抛出的错误。一般就会采用下面的写法。
Promise.resolve().then(f)
复制代码
上面的写法有一个缺点,就是如果f
是同步函数,那么它会在本轮事件循环的末尾执行
。
const f = () => console.log('now');
Promise.resolve().then(f);
console.log('next');
// next
// now
复制代码
让同步函数同步执行,异步函数异步执行,并且让它们具有统一的 API
1、async函数
const f = () => console.log('now');
(async () => f())();
console.log('next');
// now
// next
复制代码
上面代码中,第二行
是一个立即执行的匿名函数
,会立即执行里面的async函数,因此如果f
是同步的,就会得到同步的结果;
如果f是异步的,就可以用then指定下一步,就像下面的写法。
(async () => f())()
.then(...)
.catch(...) //async () => f()会吃掉f()抛出的错误,使用promise.catch捕获
复制代码
2、new Promise()
const f = () => console.log('now');
(
() => new Promise(
resolve => resolve(f())
)
)();
console.log('next');
// now
// next
复制代码
使用Promise.try
方法替代
上面的自执行函数写法
,Promise.try(f)
const f = () => console.log('now');
Promise.try(f);
console.log('next');
// now
// next
复制代码
如果想用then方法管理流程,最好都用Promise.try包装一下,可以更好地管理异常
Promise.try(() => database.users.get({id: userId}))
.then(...)
.catch(...)
复制代码