- A+
Promise是JS中用于处理异步操作的方法,- 支持链式调用从而解决了地狱回调问题。
Promise的基础用法
状态
promise有三种状态:
- Pending(待定):初始状态,既不是成功也不是失败。
- Fulfilled(已成功):操作成功完成。
- Rejected(已失败):操作失败。
const promise = new Promise((resolve, reject) => { // 异步操作 if (成功) { resolve(value); } else { reject(error); } });
实例方法
Promise
有三个实例方法,分别是then
,catch
,和finally
。
then
用于处理Promise成功的情况:
promise.then((value) => { console.log(value); });
catch
用于处理Promise失败的情况,即异常捕获:
promise.catch((error) => { console.error(error); });
finally
:无论Promise最终状态如何(成功或失败),都会执行finally中的回调。
promise.finally(() => { console.log('操作完成'); });
链式调用
then
方法可以返回一个Promise,并在后续链式地继续调用then
方法。
doSomething() .then((result) => { return doSomethingElse(result); }) .then((newResult) => { return doThirdThing(newResult); }) .then((finalResult) => { console.log(`Final result: ${finalResult}`); }) .catch((error) => { console.error(error); });
链式调用只需要在尾部调用一次catch
,在链式调用的过程中发生的异常都会被这个尾部的catch
捕获。
静态方法
Promise.resolve(value)
:返回一个成功的Promise,值为value
;常见于后面跟上then
方法将一个函数推入微任务队列;Promise.reject(reason)
:返回一个失败的Promise,原因为reason
;Promise.all(iterable)
:并行执行多个Promise,所有Promise都成功时返回一个包含所有结果的新Promise,如果有任何一个失败,则返回失败的Promise。
Promise.all([promise1, promise2, promise3]) .then((values) => console.log(values)) .catch((error) => console.error(error));
Promise.race(iterable)
:返回第一个完成的Promise,无论成功还是失败。
Promise.race([promise1, promise2, promise3]) .then((value) => console.log(value)) .catch((error) => console.error(error));
Promise.all的应用场景
并发请求,有时候在一个页面中需要使用多个GET请求获取页面数据并渲染,并且这些GET请求没有依赖关系,即不需要考虑请求顺序。那么这时就可以使用Promise.all
并发执行这些GET请求。
const fetchUser = fetch('https://api.example.com/user'); const fetchPosts = fetch('https://api.example.com/posts'); const fetchComments = fetch('https://api.example.com/comments'); Promise.all([fetchUser, fetchPosts, fetchComments]) .then(([userResponse, postsResponse, commentsResponse]) => { return Promise.all([userResponse.json(), postsResponse.json(), commentsResponse.json()]); }) .then(([userData, postsData, commentsData]) => { console.log(userData, postsData, commentsData); }) .catch((error) => { console.error('请求失败', error); });
并发执行需要注意并发量不要太大,我们可以通过实现一个并发控制的类来限制并发量。
class RequestScheduler { constructor(concurrencyLimit) { this.concurrencyLimit = concurrencyLimit; this.running = 0; this.queue = []; } // 添加请求到队列 add(requestFn) { return new Promise((resolve, reject) => { this.queue.push({ requestFn, resolve, reject }); this.runNext(); }); } // 执行下一个请求 runNext() { if (this.running >= this.concurrencyLimit || this.queue.length === 0) { return; } const { requestFn, resolve, reject } = this.queue.shift(); this.running++; requestFn() .then((result) => { resolve(result); }) .catch((error) => { reject(error); }) .finally(() => { this.running--; this.runNext(); }); } } // 使用示例 const scheduler = new RequestScheduler(3); // 限制并发请求数量为3 const createRequest = (url) => () => fetch(url).then((response) => response.json()); const urls = [ 'https://jsonplaceholder.typicode.com/posts/1', 'https://jsonplaceholder.typicode.com/posts/2', 'https://jsonplaceholder.typicode.com/posts/3', 'https://jsonplaceholder.typicode.com/posts/4', 'https://jsonplaceholder.typicode.com/posts/5' ]; const requestPromises = urls.map((url) => scheduler.add(createRequest(url))); Promise.all(requestPromises) .then((results) => { console.log('所有请求完成:', results); }) .catch((error) => { console.error('请求失败:', error); });
createRequest
方法生成返回Promise的请求函数;scheduler.add
方法将一个请求添加到调度器中,并在并发限制允许的情况下执行;Promise.all
的作用是等待所有请求完成,并且统一处理异常。
Promise.race的应用场景
Promise.race方法关注的是最快出结果(不管是fulfilled
还是rejected
)的promise,可以实现超时处理。
超时处理:在race中传入网络请求的promise和定时器的promise,如果网络请求在指定时间内到达则正常执行then流程,如果定时器先到达则表示超时,调用reject走catch流程。
const fetchWithTimeout = (url, timeout) => { const fetchPromise = fetch(url); const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('请求超时')), timeout)); return Promise.race([fetchPromise, timeoutPromise]); }; fetchWithTimeout('https://api.example.com/data', 5000) .then((response) => response.json()) .then((data) => { console.log('请求成功', data); }) .catch((error) => { console.error('请求失败或超时', error); });
Promise.allSettled
Promise.allSettled 方法返回一个在所有给定的 Promise 已经 fulfilled 或 rejected 后的 Promise,并且带有一个对象数组,每个对象表示对应的 Promise 结果。
如果是fulfilled
,则结果字段为value
;
如果是rejected
,则结果字段为reason
。
const promises = [ Promise.resolve('resolved'), Promise.reject('rejected'), new Promise((resolve) => setTimeout(resolve, 1000, 'pending resolved')) ]; Promise.allSettled(promises) .then((results) => { results.forEach((result) => console.log(result)); }); // 输出: // { status: 'fulfilled', value: 'resolved' } // { status: 'rejected', reason: 'rejected' } // { status: 'fulfilled', value: 'pending resolved' }
Promise.any
接受一个promise数组,返回一个promise。
和Promise.race不同,Promise.any会过滤掉所有rejected
的promise,而关注第一个fulfilled
的promise的值。
如果数组中所有promise都被rejected
的话,那么会返回一个AggregateError类型的实例,带有errors
字段,是一个数组,指明了每一个promise的reason
。
应用场景:any可以用来在多个备用资源中获取最先成功响应的资源。
最快成功返回的备用资源:假设一个数据有多个可用来源,我们只需要拿到其中一个成功响应就可以了,那么肯定是想要拿最快返回的那一个,这个时候用any就很nice~
const loadImage = (url) => new Promise((resolve, reject) => { const img = new Image(); img.onload = () => resolve(url); img.onerror = () => reject(new Error(`Failed to load image at ${url}`)); img.src = url; }); const imageUrls = ['image1.png', 'image2.png', 'image3.png']; const imagePromises = imageUrls.map(loadImage); Promise.any(imagePromises) .then((result) => { console.log('第一个加载完成的图片', result); }) .catch((error) => { console.error('所有图片加载失败', error); });
Promise.withResolvers
这个方法返回一个新的promise对象和用于解决或拒绝它的resolve
和reject
方法。
可以简单地使用Promise手动实现:
Promise.withResolvers = function() { let resolve, reject; const promise = new Promise((res, rej) => { resolve = res; reject = rej; }); return { promise, resolve, reject }; };
使用 Promise.withResolvers() 关键的区别在于解决和拒绝函数现在与 Promise 本身处于同一作用域,而不是在执行器中被创建和一次性使用。
通常在一些重复事件中使用,例如在处理流数据或者队列的时候,在这些场景下通常可以减少嵌套,优化代码结构。
这里介绍MDN上面的案例:将流转换为异步可迭代对象。
// 定义 async generator 函数 readableToAsyncIterable,将流转换为异步可迭代对象 async function* readableToAsyncIterable(stream) { // 创建 Promise 和解析器对象 let { promise, resolve, reject } = Promise.withResolvers(); // 监听流的错误事件,一旦出错则调用 reject 方法 stream.on("error", (error) => reject(error)); // 监听流的结束事件,一旦结束则调用 resolve 方法 stream.on("end", () => resolve()); // 监听流的可读事件,一旦流准备好可以读取则调用 resolve 方法 stream.on("readable", () => resolve()); // 循环处理流中的数据块,直到流不再可读 while (stream.readable) { // 等待当前的 Promise 解决 await promise; let chunk; // 循环读取流中的数据块 while ((chunk = stream.read())) { // 生成数据块 yield chunk; } // 获取新的 Promise 和解析器对象,以便下一轮循环使用 ({ promise, resolve, reject } = Promise.withResolvers()); } }
创建一个简单的可读流测试一下:
const { Readable } = require('stream'); // 测试函数 async function testReadableToAsyncIterable() { // 创建一个简单的可读流 const data = ['Hello', 'World']; const readableStream = Readable.from(data); // 将可读流转换为异步可迭代对象 const asyncIterable = readableToAsyncIterable(readableStream); // 使用 for await...of 循环遍历异步可迭代对象中的数据块,并验证结果 let result = ''; for await (const chunk of asyncIterable) { result += chunk.toString(); } // 断言结果是否符合预期 if (result === data.join('')) { console.log('测试通过:数据正常读取和处理。'); } else { console.error('测试失败:数据读取和处理出现问题。'); } } // 执行测试函数 testReadableToAsyncIterable();
Promise规范与手写Promise
👉 Promises/A+ 规范
示例代码:
const PENDING = 'pending'; const FULFILLED = 'fulfilled'; const REJECTED = 'rejected'; class MyPromise { constructor(executor) { this.state = PENDING; // 初始状态为 pending this.value = undefined; // 成功时的值 this.reason = undefined; // 失败时的原因 this.onFulfilledCallbacks = []; // 存储成功时的回调函数 this.onRejectedCallbacks = []; // 存储失败时的回调函数 // 定义 resolve 函数,用于将状态转变为 fulfilled,并执行成功的回调函数 const resolve = (value) => { if (this.state === PENDING) { this.state = FULFILLED; this.value = value; // 执行所有成功回调函数 this._executeCallbacks(this.onFulfilledCallbacks, this.value); } }; // 定义 reject 函数,用于将状态转变为 rejected,并执行失败的回调函数 const reject = (reason) => { if (this.state === PENDING) { this.state = REJECTED; this.reason = reason; // 执行所有失败回调函数 this._executeCallbacks(this.onRejectedCallbacks, this.reason); } }; try { // 执行执行器函数,并传入 resolve 和 reject 函数 executor(resolve, reject); } catch (error) { // 如果执行器函数抛出异常,则直接 reject reject(error); } } // 定义 then 方法,用于链式调用 then(onFulfilled, onRejected) { return new MyPromise((resolve, reject) => { // 定义处理成功的函数 const handleFulfilled = (value) => { try { // 如果 onFulfilled 存在,则执行它,并获取结果 const result = onFulfilled ? onFulfilled(value) : value; this._handleResult(result, resolve, reject); } catch (error) { reject(error); } }; // 定义处理失败的函数 const handleRejected = (reason) => { try { // 如果 onRejected 存在,则执行它,并获取结果 const result = onRejected ? onRejected(reason) : reason; this._handleResult(result, resolve, reject); } catch (error) { reject(error); } }; // 根据当前 Promise 的状态执行不同的逻辑 if (this.state === FULFILLED) { // 使用 queueMicrotask 来模拟微任务,确保在当前事件循环结束后执行 handleFulfilled queueMicrotask(() => handleFulfilled(this.value)); } else if (this.state === REJECTED) { // 使用 queueMicrotask 来模拟微任务,确保在当前事件循环结束后执行 handleRejected queueMicrotask(() => handleRejected(this.reason)); } else if (this.state === PENDING) { // 如果当前状态仍为 pending,则将处理成功和失败的函数加入对应的回调数组中 this.onFulfilledCallbacks.push(value => { queueMicrotask(() => handleFulfilled(value)); }); this.onRejectedCallbacks.push(reason => { queueMicrotask(() => handleRejected(reason)); }); } }); } // 定义 catch 方法,用于捕获 Promise 链中的错误 catch(onRejected) { return this.then(null, onRejected); } // 静态方法 resolve,返回一个立即 resolved 的 Promise 对象 static resolve(value) { return new MyPromise((resolve) => { resolve(value); }); } // 静态方法 reject,返回一个立即 rejected 的 Promise 对象 static reject(reason) { return new MyPromise((resolve, reject) => { reject(reason); }); } // 静态方法 all,接收一个 Promise 数组,返回一个新的 Promise,当所有 Promise 都成功时才成功,结果为一个值数组 static all(promises) { return new MyPromise((resolve, reject) => { const results = []; let count = 0; promises.forEach((promise, index) => { promise.then((value) => { results[index] = value; count++; // 当所有 Promise 都成功时,resolve 结果数组 if (count === promises.length) { resolve(results); } }).catch(reject); // 一旦有 Promise 失败,则整体 Promise 也失败 }); }); } // 静态方法 race,接收一个 Promise 数组,返回一个新的 Promise,以最先 resolved 或 rejected 的 Promise 的结果作为结果 static race(promises) { return new MyPromise((resolve, reject) => { // 遍历 Promise 数组,一旦有 Promise 解决或拒绝,则立即 resolve 或 reject promises.forEach((promise) => { promise.then(resolve).catch(reject); }); }); } // 私有方法,用于处理 then 方法返回的结果 _handleResult(result, resolve, reject) { if (result instanceof MyPromise) { // 如果返回结果是一个 Promise 实例,则继续链式调用 result.then(resolve, reject); } else { // 否则直接将结果传递给下一个 Promise 的 resolve resolve(result); } } // 私有方法,用于执行回调函数数组中的所有回调 _executeCallbacks(callbacks, arg) { callbacks.forEach(callback => { // 使用 queueMicrotask 来模拟微任务,确保在当前事件循环结束后执行回调 queueMicrotask(() => callback(arg)); }); } }
为什么使用数组存储回调函数?
通常我们在使用promise的时候只会调用一次then
方法并传入一个回调函数,但其实then
方法是可以多次调用的,例如下面这段代码,则会添加多个回调,因此需要使用数组存储回调函数。
const promise = new Promise((resolve, reject) => { // 执行异步操作 setTimeout(() => { resolve('成功'); // 改变状态为 fulfilled }, 1000); }); promise.then( value => console.log('成功处理1:', value), reason => console.error('失败处理1:', reason) ); promise.then( value => console.log('成功处理2:', value), reason => console.error('失败处理2:', reason) );