[JS] promise知识点与应用场景

cnblogs 2024-07-06 08:11:01 阅读 87

本文探讨了JavaScript中Promise的基础用法和各种静态方法的应用场景。从解决异步编程中的回调地狱问题,到链式调用、并发请求控制,再到最新的Promise.allSettled和Promise.any的应用。每种方法均通过代码示例和详细的应用场景进行了展示。

Promise是JS中用于处理异步操作的方法,- 支持链式调用从而解决了地狱回调问题。

Promise的基础用法

状态

promise有三种状态:

  • Pending(待定):初始状态,既不是成功也不是失败。
  • Fulfilled(已成功):操作成功完成。
  • Rejected(已失败):操作失败。

<code>const promise = new Promise((resolve, reject) => {

// 异步操作

if (成功) {

resolve(value);

} else {

reject(error);

}

});

实例方法

Promise有三个实例方法,分别是thencatch,和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对象和用于解决或拒绝它的resolvereject方法。

可以简单地使用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)

);



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。