前端 Promise 博客笔记
在前端开发中,异步操作是非常常见的,比如网络请求、定时器等。而 Promise 作为处理异步操作的重要方案,极大地改善了异步代码的可读性和可维护性。下面就来详细梳理一下 Promise 的相关知识。
一、Promise 的基本概念
Promise 是一个对象,它代表了一个异步操作的最终完成或者失败,及其结果值。简单来说,Promise 就像一个容器,里面存放着某个未来才会结束的事件(通常是一个异步操作)的结果。
Promise 有三种状态:
- pending(进行中):初始状态,既不是成功,也不是失败。
- fulfilled(已成功):异步操作完成,此时会调用 then 方法指定的回调函数。
- rejected(已失败):异步操作失败,此时会调用 catch 方法指定的回调函数。
需要注意的是,Promise 的状态一旦改变,就会永久保持该状态,不会再发生变化。状态的改变只有两种可能:从 pending 变为 fulfilled,或者从 pending 变为 rejected。
二、Promise 的基本用法
创建一个 Promise 实例需要传入一个 executor 函数,该函数有两个参数,分别是 resolve 和 reject。resolve 函数的作用是将 Promise 的状态从 pending 变为 fulfilled,并将异步操作的结果作为参数传递出去;reject 函数的作用是将 Promise 的状态从 pending 变为 rejected,并将异步操作失败的原因作为参数传递出去。
const promise = new Promise((resolve, reject) => {
// 异步操作
setTimeout(() => {
const randomNum = Math.random();
if (randomNum > 0.5) {
resolve(randomNum); // 成功时调用resolve
} else {
reject(new Error('随机数小于等于0.5')); // 失败时调用reject
}
}, 1000);
});
三、Promise 的常用方法
then () 方法
then () 方法用于指定当 Promise 状态变为 fulfilled 时的回调函数。它可以接收两个参数,第一个参数是 fulfilled 状态的回调函数,第二个参数是 rejected 状态的回调函数(可选)。then () 方法返回一个新的 Promise 实例,因此可以采用链式调用的方式。
promise.then(
(value) => {
console.log('成功:', value);
},
(error) => {
console.log('失败:', error);
}
);
catch () 方法
catch () 方法用于指定当 Promise 状态变为 rejected 时的回调函数,它相当于 then (null, rejection)。同时,catch () 方法还可以捕获前面 then () 方法执行过程中抛出的错误。
promise
.then((value) => {
console.log('成功:', value);
throw new Error('在then中抛出错误');
})
.catch((error) => {
console.log('失败:', error);
});
finally () 方法
finally () 方法用于指定无论 Promise 状态是 fulfilled 还是 rejected,都会执行的操作。它的回调函数不接收任何参数,因为无法知道 Promise 的最终状态。
promise
.then((value) => {
console.log('成功:', value);
})
.catch((error) => {
console.log('失败:', error);
})
.finally(() => {
console.log('无论成功失败都会执行');
});
四、Promise 的静态方法
Promise.all()
该方法接收一个可迭代对象(通常是数组)作为参数,返回一个新的 Promise。当数组中所有的 Promise 都变为 fulfilled 状态时,新的 Promise 才会变为 fulfilled 状态,其结果是所有 Promise 结果组成的数组;如果数组中有一个 Promise 变为 rejected 状态,新的 Promise 就会立即变为 rejected 状态,其结果是该 rejected 的原因。
const promise1 = Promise.resolve(1);
const promise2 = Promise.resolve(2);
const promise3 = Promise.resolve(3);
Promise.all([promise1, promise2, promise3])
.then((values) => {
console.log(values); // [1, 2, 3]
})
.catch((error) => {
console.log(error);
});
Promise.race()
race () 方法同样接收一个可迭代对象作为参数,返回一个新的 Promise。只要数组中有一个 Promise 的状态发生改变(无论是 fulfilled 还是 rejected),新的 Promise 就会立即以相同的状态和结果改变。
const promise1 = new Promise((resolve) => {
setTimeout(resolve, 100, 'one');
});
const promise2 = new Promise((resolve) => {
setTimeout(resolve,200, 'two');
});
Promise.race([promise1, promise2])
.then((value) => {
console.log(value); // 'one',因为promise1先完成
});
Promise.resolve()
该方法返回一个以给定值解析后的 Promise 实例。如果该值是一个 Promise,那么返回的 Promise 会采用它的状态;如果该值是一个 thenable 对象(即具有 then 方法),则会将其转换为 Promise 并执行 then 方法;否则,返回的 Promise 状态为 fulfilled,结果为该值。
const promise = Promise.resolve(42);
promise.then((value) => {
console.log(value); // 42
});
Promise.reject()
该方法返回一个状态为 rejected 的 Promise 实例,结果为给定的错误信息。
const promise = Promise.reject(new Error('出错了'));
promise.catch((error) => {
console.log(error); // Error: 出错了
});
五、Promise 解决的问题
在 Promise 出现之前,我们处理异步操作主要使用回调函数。但是,当存在多个嵌套的异步操作时,就会产生 “回调地狱”,代码的可读性和可维护性极差。
例如,多层嵌套的回调:
setTimeout(() => {
console.log('第一层');
setTimeout(() => {
console.log('第二层');
setTimeout(() => {
console.log('第三层');
// 更多层嵌套...
}, 1000);
}, 1000);
}, 1000);
而使用 Promise 可以将嵌套的回调改为链式调用,使代码更加清晰、直观:
new Promise((resolve) => {
setTimeout(() => {
console.log('第一层');
resolve();
}, 1000);
})
.then(() => {
return new Promise((resolve) => {
setTimeout(() => {
console.log('第二层');
resolve();
}, 1000);
});
})
.then(() => {
setTimeout(() => {
console.log('第三层');
}, 1000);
});
六、使用 Promise 的注意事项
- 避免在 Promise 内部使用同步代码,因为 Promise 的回调函数是异步执行的,同步代码可能会导致不符合预期的结果。
- 不要忘记处理 Promise 的错误,否则错误可能会被默默忽略,难以排查。可以使用 catch () 方法统一捕获错误。
- 在 then () 方法中返回一个新的 Promise,可以实现异步操作的串行执行;使用 Promise.all () 可以实现异步操作的并行执行,但要注意其中一个失败会导致整个 Promise.all () 失败。
- 不要过度使用 Promise,对于简单的异步操作,使用回调函数可能更加简洁。
总之,Promise 是前端处理异步操作的重要工具,掌握它的使用方法和相关特性,能够极大地提高异步代码的质量和开发效率。在实际开发中,我们还会结合 async/await 等语法,让异步代码更加简洁、易读。