概念
简单的说Promise就是一个容器,里面保存着某个未来才会结束的事件的结果。通常是异步操作的结果。
特点
- 对象的状态不受外界影响。
Promise对象代表一个异步操作,有三种状态:
- pending:进行中
- fulfilled:已成功
- rejected:已失败 只有异步操作的结果可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。
- 一旦状态改变,就不会再变,任何时候都可以得到这个结果。
缺点
- 无法取消Promise,一旦新建它就会立即执行,无法在中途取消
- 如果不设置回调函数,Promise内部抛出的错误,不会反映到外部
- 当处于pending状态时,无法得知目前进展到哪一阶段
基本用法
Promise对象是一个构造函数,用来生成Promise实例。
let isSuccess = true
const promise = new Promise(function(resolve,reject){
if(isSuccess){
resolve('成功')
}else{
reject('失败')
}
})
promise创建成功后,可以通过.then来获取结果
const promise = new Promise(function(resolve,reject){
if(isSuccess){
resolve('成功')
}else{
reject('失败')
}
})
promise.then(value => {
console.log(value)
})
示例:
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;
});
}
// 正确用法
loadImageAsync('./bg.png').then((image) => {
document.querySelector('div').appendChild(image);
}).catch((error) => {
console.error('图片加载失败:', error);
});
图片验证
// 验证图片链接是否有效
async function validateImage(url) {
try {
await loadImageAsync(url);
return true; // 图片有效
} catch {
return false; // 图片无效
}
}
Promise.prototype.then()
then方法时定义在原型对象上的,作用时Promise实例添加状态改变时的回调函数。
- then有两个参数,分别时成功和失败的回调:
<script>
let isLike = false
const promise = new Promise((resolve,reject)=>{
if(isLike){
resolve('成功')
}else{
reject('失败')
}
})
promise.then(success=>{
console.log(success)
},error=>{
console.log(error)
})
</script>
- then方法的链式调用
可以指定一组按照次序调用的回调函数。
前一个回调函数,有可能返回的还是一个promise对象,这时后一个回调函数,就会等待该promise对象的状态发生变化,才会被调用。简单的说就是后一个then方法的执行时机就是上一个回调函数的结果发生变化的时候。
关于then方法的链式用法,我们来看几个例子:
- 用户注册流程
registerUser(userData)
.then(user => login(user.email, user.password))
.then(token => getUserProfile(token))
.then(profile => updateUserPreferences(profile.id, preferences))
.then(result => {
console.log('注册流程完成');
})
.catch(error => {
console.error('注册失败:', error);
});
值得注意的是,如果这个流程中有一个promise返回了reject,那么就会直接去执行catch方法,来捕获错误,终止整个函数的执行
Promise.prototype.catch()
这个方法和then方法的第二个参数的行为是相似的,用来获取到reject返回的结果,也就是发生错误的回调函数。
const promise = new Promise(function(resolve, reject) {
throw new Error('test');
});
promise.catch(function(error) {
console.log(error);
});
如果promise的状态已经凝固,那么catch就一定不会被触发 ==值得注意的是,我们尽量不要使用then方法的第二个参数来捕获promise的错误,尽量来使用catch方法实现==
==如果没有使用catch方法指定错误处理的回调函数,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
someAsyncThing()函数产生的 Promise 对象,==内部有语法错误==。浏览器运行到这一行,会打印出错误提示==ReferenceError: x is not defined==,但是==不会退出进程、终止脚本执行==,2 秒之后还是会输出123。这就是说,==Promise 内部的错误不会影响到 Promise 外部的代码,通俗的说法就是“Promise 会吃掉错误”==。
虽然说catch是用来捕获promise内部的错误的,但是它也可以在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()
finally方法用于指定不论Promise对象最后状态如何,都会执行的操作
promise
.finally(() => {
// 语句
});
// 等同于
promise
.then(
result => {
// 语句
return result;
},
error => {
// 语句
throw error;
}
);
Promise.all()
这个方法用于将多个Promise实例,包装成一个新的Promise实例。
const p = Promise.all([p1,p2,p3]);
all方法接受一个数组作为参数,数组中都是Promise的实例,如果不是,那么就先调用==resolve==方法,将参数转为Promise实例
p的状态取决于p1,p2,p3,这三个实例的状态:
- 如果三个都返回成功,那么就将这三个的返回值组成一个数组,传递给p的回调函数
- 但是只要其中有一个实例返回了reject,那么就会将这个实例返回的reject的值传递给p的回调函数 举个例子:
const promises = [2, 3, 5, 7, 11, 13].map(function (id) {
return getJSON('/post/' + id + ".json");
});
Promise.all(promises).then(function (posts) {
// ...
}).catch(function(reason){
// ...
});
注意:
如果作为参数的Promise实例,定义了自己的catch方法,那么一旦被rejected,并不会触发all方法的catch方法
Promise.race()
这个方法也是将多个Promise实例,包装成一个新的Promise实例。
const p = Promise.race([p1, p2, p3]);
只要p1,p2,p3中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的Promise实例的返回值,就传递给p的回调函数。
Promise.resolve()
将现有的对象转为Promise对象 ==这个方法的参数分为四种情况:==
- 参数是一个==Promise实例== 如果参数是一个Promise实例,那么Promise.resolve将不做任何修改,==原封不懂地返回这个实例==
- 参数是一个==thenable对象== thenable对象指的是具有then的对象,例如:
let thenable = {
then:function(resolve,reject){
resolve(43)
}
}
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
});
- 参数不是具有then()方法的对象,或者根本就不是对象 如果参数是一个原始值,或者是一个不具有then()方法的对象,那么resolve方法返回一个新的Promise对象,状态为resolved。
const p = Promise.resolve('hello');
p.then(s=>{
console.log(s)
})
- 不带任何参数 直接返回一个resolved状态的Promise对象。
setTimeout(function () {
console.log("three");
}, 0);
Promise.resolve().then(function () {
console.log("two");
});
console.log("one");
![[Pasted image 20251117195131.png]] ==需要注意的是:立即执行resolve()的Promise对象,是在本轮“事件循环”的结束时执行,而不是在下一轮“事件循环”的开始时==
Promise.allSettled()
Promise.allSettled()方法,用来确定一组异步操作是否都结束了(不管成功或失败)。
allSettled方法接收一个数组作为参数,数组中的每一个成员都是一个Promise对象,并返回一个新的Promise对象。只有等到所有的Promise对象有状态变更,返回的Promise对象才会发生状态变更。
==该方法返回的新的 Promise 实例,一旦发生状态变更,状态总是fulfilled,不会变成rejected。状态变成fulfilled后,它的回调函数会接收到一个数组作为参数,该数组的每个成员对应前面数组的每个 Promise 对象。==
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 }
// ]
固定的返回格式
// 异步操作成功时
{status: 'fulfilled', value: value}
// 异步操作失败时
{status: 'rejected', reason: reason}
Promise.any()
该方法接收一组Promise实例作为参数,包装成一个新的Promise实例返回 ==只要有一个实例返回了成功,那么就将这个成功的状态传递给any方法的实例返回,只有全部实例返回rejected,才会返回rejected==
Promise.reject()
Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为rejected。
const p = Promise.reject('出错了');
// 等同于
const p = new Promise((resolve, reject) => reject('出错了'))
p.then(null, function (s) {
console.log(s)
});
// 出错了
上面代码生成一个 Promise 对象的实例p,状态为rejected,回调函数会立即执行。
Promise.reject()方法的参数,会原封不动地作为reject的理由,变成后续方法的参数。
Promise.reject('出错了')
.catch(e => {
console.log(e === '出错了')
})
// true
上面代码中,Promise.reject()方法的参数是一个字符串,后面catch()方法的参数e就是这个字符串。
Promise.try()
实际开发中,经常遇到一种情况:不知道或者不想区分,函数f是同步函数还是异步操作,但是想用 Promise 来处理它。因为这样就可以不管f是否包含异步操作,都用then方法指定下一步流程,用catch方法处理f抛出的错误。一般就会采用下面的写法。
Promise.resolve().then(f)
上面的写法有一个缺点,就是如果f是同步函数,那么它会在本轮事件循环的末尾执行。
const f = () => console.log('now');
Promise.resolve().then(f);
console.log('next');
// next
// now
上面代码中,函数f是同步的,但是用 Promise 包装了以后,就变成异步执行了。
那么有没有一种方法,让同步函数同步执行,异步函数异步执行,并且让它们具有统一的 API 呢?回答是可以的,并且还有两种写法。第一种写法是用async函数来写。
const f = () => console.log('now');
(async () => f())();
console.log('next');
// now
// next
上面代码中,第二行是一个立即执行的匿名函数,会立即执行里面的async函数,因此如果f是同步的,就会得到同步的结果;如果f是异步的,就可以用then指定下一步,就像下面的写法。
(async () => f())()
.then(...)
需要注意的是,async () => f()会吃掉f()抛出的错误。所以,如果想捕获错误,要使用promise.catch方法。
(async () => f())()
.then(...)
.catch(...)
第二种写法是使用new Promise()。
const f = () => console.log('now');
(
() => new Promise(
resolve => resolve(f())
)
)();
console.log('next');
// now
// next
上面代码也是使用立即执行的匿名函数,执行new Promise()。这种情况下,同步函数也是同步执行的。
鉴于这是一个很常见的需求,所以 ES2025 提供了Promise.try()方法替代上面的写法。
const f = () => console.log('now');
Promise.try(f);
console.log('next');
// now
// next
事实上,Promise.try存在已久,Promise 库Bluebird、Q和when,早就提供了这个方法。
由于Promise.try为所有操作提供了统一的处理机制,所以如果想用then方法管理流程,最好都用Promise.try包装一下。这样有许多好处,其中一点就是可以更好地管理异常。
function getUsername(userId) {
return database.users.get({id: userId})
.then(function(user) {
return user.name;
});
}
上面代码中,database.users.get()返回一个 Promise 对象,如果抛出异步错误,可以用catch方法捕获,就像下面这样写。
database.users.get({id: userId})
.then(...)
.catch(...)
但是database.users.get()可能还会抛出同步错误(比如数据库连接错误,具体要看实现方法),这时你就不得不用try...catch去捕获。
try {
database.users.get({id: userId})
.then(...)
.catch(...)
} catch (e) {
// ...
}
上面这样的写法就很笨拙了,这时就可以统一用promise.catch()捕获所有同步和异步的错误。
Promise.try(() => database.users.get({id: userId}))
.then(...)
.catch(...)
事实上,Promise.try就是模拟try代码块,就像promise.catch模拟的是catch代码块。