Promise 实战案例

关于 Promise 实战的几个案例

本文是关于一次面试的记录,从而引发的进一步扩展。

初级:如何同时发起十个请求

一看到这个标题,我们很容易的就想到可以用 Promise.all() 进行实现。

以下是实现的具体代码。

js 复制代码
// 假设我们有一个包含URLs的数组
const urls = [
    'https://api.example.com/data/1',
    'https://api.example.com/data/2',
    // ... 更多URLs
    'https://api.example.com/data/10'
];

// 定义一个异步的fetchData函数来发起请求
async function fetchData(url) {
    try {
        const response = await fetch(url);
        if (!response.ok) {
            throw new Error(`HTTP error! Status: ${response.status}`);
        }
        return response.json(); // 假设API返回JSON格式的数据
    } catch (error) {
        console.error(`Failed to fetch data from ${url}: `, error);
        throw error; // 重新抛出错误,以便Promise.all能捕获
    }
}

// 使用Promise.all同时发起所有请求
Promise.all(urls.map(fetchData))
    .then(dataArrays => {
        // dataArrays是一个数组,包含了每个fetchData调用的结果
        // 这里可以处理所有请求成功的数据
        console.log('All requests completed successfully:', dataArrays);
    })
    .catch(error => {
        // 这里处理如果有任何请求失败的情况
        console.error('At least one request failed:', error);
    });

这里还可以再看看 Promise 的其他方法和它们之间的区别。

  1. Promise.all(),接收一个由 Promise 组成的数组。 当全部成功时,返回全部的值,当任何一个失败时,只返回首次失败的原因。
js 复制代码
const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve, reject) => {
  setTimeout(resolve, 100, 'foo');
});

Promise.all([promise1, promise2, promise3]).then((values) => {
  console.log(values);
});
// Expected output: Array [3, 42, "foo"]
  1. Promise.allSettled(),接收一个由 Promise 组成的数组。 会返回每一个 Promise 的结果,它的返回结构为 { status: "rejected", reason: "foo" } 或者 { status: 'fulfilled', value: 99 },这取决于 Promise 的状态。最后把所有的结果合并到一个数组中。
js 复制代码
Promise.allSettled([
  Promise.resolve(33),
  new Promise((resolve) => setTimeout(() => resolve(66), 0)),
  99,
  Promise.reject(new Error("一个错误")),
]).then((values) => console.log(values));

// [
//   { status: 'fulfilled', value: 33 },
//   { status: 'fulfilled', value: 66 },
//   { status: 'fulfilled', value: 99 },
//   { status: 'rejected', reason: Error: 一个错误 }
// ]
  1. Promise.race(),接收一个由 Promise 组成的数组。返回最先完成的 Promise 的结果。
js 复制代码
const promise1 = new Promise((resolve, reject) => {
  setTimeout(resolve, 500, 'one');
});

const promise2 = new Promise((resolve, reject) => {
  setTimeout(resolve, 100, 'two');
});

Promise.race([promise1, promise2]).then((value) => {
  console.log(value);
  // Both resolve, but promise2 is faster
});
// Expected output: "two"

进阶:如何对发起的请求进行并发控制

控制并发数量

思路:

  1. 维护一个队列,存储的是 请求的 url
  2. 几个控制变量
    1. 并发请求数量
    2. 当前队列
    3. 当前请求数量
  3. 请求开始的执行条件用 while 去做
  4. 请求结束的标识 用 setInterval 循环去监听
js 复制代码
function fetchWithLimit(urls, limit = 5) {
    const queue = [];
    const results = [];
    let activeCount = 0;

    // 初始化队列
    urls.forEach(url => {
        queue.push(url)
    });

    function fakeFetch(url) {
        return new Promise((resolve) => {
            setTimeout(() => {
                resolve(url)
                console.log('请求执行完毕', url);
            }, 100);
        })
    }


    function fetchURL() {
        if (queue.length > 0 && activeCount < limit) {
            activeCount++
            const url = queue.shift()
            console.log('请求开始', url)
            fakeFetch(url).then((res) => {
                results.push(res)
            })
                .catch((err) => console.log(err))
                .finally(() => {
                    activeCount--
                    console.log('请求结束', url)
                    fetchURL()
                })
        }
    }

    // 开始处理队列
    while (activeCount < limit) {
        fetchURL();
    }

    return new Promise((resolve) => {
        const timer = setInterval(() => {
            if (queue.length === 0 && activeCount === 0) {
                clearInterval(timer)
                resolve(results)
            }
        }, 100);
    })
}
const urls = Array.from({ length: 70 }, (_, i) => `https://api.example.com/data/${i}`);

fetchWithLimit(urls, 5).then((res) => console.log('最终结果', res))

高级:如何取消一个正在进行的请求

这里很容易的可以想到用Promise.race() 去做竞态的控制。当需要取消请求时,想办法触发其中一个 Promise 使其状态变为 fulfilled 或者 Rejected。那么整个包括 Promise.race() 的请求就完成了。

那么问题来了,如何去触发 Promise 使其改变状态呢。

我们可以思考下,在 axios 中,是如何进行一个请求的取消。

js 复制代码
const controller = new AbortController()

aiios.get('/foo/bar', {
    singal: controller.singal
}).then(fuction(res)
    //...
)


// 调用中止请求的方法
controller.abort()

如果对于 AbortController 不熟悉的,我们可以来看一段 MDN 关于它的介绍:

AbortController 接口表示一个控制器对象,允许你根据需要中止一个或多个 Web 请求。 你可以使用 AbortController.AbortController() 构造函数创建一个新的 AbortController。使用 AbortSignal 对象可以完成与 DOM 请求的通信。

通过上面的代码,我们可以判断。

axios 内部封装AbortController以支持取消请求的方式主要是通过将用户提供的signal选项与fetch请求或者 XMLHttpRequest 的中止机制集成。

  1. 接收signal选项 :当创建 axios 请求时,用户可以向配置对象中传递一个从AbortController.signal获取的AbortSignal实例。
javascript 复制代码
const controller = new AbortController();
axios.get('/api/data', {
    signal: controller.signal
});
  1. 集成到请求流程 :在axios的请求处理逻辑中,它会检查是否有提供的signal。如果有,它会把这个signal与实际的网络请求(无论是使用fetch还是XMLHttpRequest)关联起来。
  • 对于基于fetch的请求,直接将signal作为选项传递给fetch,因为fetch原生支持AbortSignal

    javascript 复制代码
    fetch(url, {
        method: 'GET',
        signal: config.signal, // 这里的config.signal就是用户传入的AbortSignal
    })
  • 对于基于XMLHttpRequest的兼容性处理,axios会监听signalabort事件,当事件触发时手动调用XMLHttpRequest.abort()来取消请求。

  1. 处理取消事件 :当AbortControllerabort()方法被调用时,其关联的AbortSignal会触发abort事件。axios监听这个事件,并在接收到信号时执行相应的清理工作,包括但不限于停止请求、清理定时器以及正确地解析和传递错误信息。

  2. 错误处理 :当请求因取消而中止时,axios会捕获到一个错误,并在.catch处理中可以通过检查错误的类型(通常是DOMException,错误名为AbortError)来识别请求是否因取消而失败。

这样,axios通过将用户提供的AbortSignal与底层网络请求的取消机制绑定,实现了请求的可取消性,同时也保持了错误处理的一致性和易用性。

所以,要手动实现可中断的请求,关键点如下:

  1. 使用 Promise.race(),包括 提供取消功能的Promise 和 真正请求的Promise。
  2. 使用 new AbortController() 构造函数,创建出一个新的 AbortController 对象实例。
  3. 调用 AbortController 对象实例上的 abort() 方法。
  4. 监听 AbortController 对象实例上的 abort() 事件执行。
  5. 当监听到第4点事件执行时,调用 提供取消功能的Promise 的 Reject() 回调,从而中止 Promise.race()

然后我们就可以写出如下代码:

js 复制代码
function cancellableFetch(url, controller) {
    // 创建实际的fetch请求Promise
    const fetchPromise = fetch(url).then(response => response.text());
    
    // 创建一个与AbortController关联的Promise,用于模拟取消操作
    const cancelPromise = new Promise((resolve, reject) => {
        controller.signal.addEventListener('abort', () => {
            reject(new Error('Fetch cancelled'));
        });
    });

    // 使用Promise.race()来决定是fetch完成还是操作被取消
    return Promise.race([fetchPromise, cancelPromise]);
}

// 创建AbortController实例
const controller = new AbortController();

// 尝试发起一个可取消的fetch请求
cancellableFetch('https://api.example.com/data', controller)
    .then(data => console.log('Data received:', data))
    .catch(error => {
        if (error.message === 'Fetch cancelled') {
            console.log('Fetch was cancelled');
        } else {
            console.error('An error occurred:', error);
        }
    });

// 假设在某个时刻用户决定取消请求
setTimeout(() => {
    controller.abort(); // 触发取消操作
}, 6000); // 例如,如果请求超过6秒未完成则取消

结语

以上就是关于 Promise 的一些实战案例了,需要大家有所收获。

相关推荐
速盾cdn24 分钟前
速盾:网页游戏部署高防服务器有什么优势?
服务器·前端·web安全
小白求学126 分钟前
CSS浮动
前端·css·css3
什么鬼昵称27 分钟前
Pikachu-csrf-CSRF(POST)
前端·csrf
golitter.44 分钟前
Vue组件库Element-ui
前端·vue.js·ui
儒雅的烤地瓜1 小时前
JS | JS中判断数组的6种方法,你知道几个?
javascript·instanceof·判断数组·数组方法·isarray·isprototypeof
道爷我悟了1 小时前
Vue入门-指令学习-v-on
javascript·vue.js·学习
27669582921 小时前
京东e卡滑块 分析
java·javascript·python·node.js·go·滑块·京东
golitter.1 小时前
Ajax和axios简单用法
前端·ajax·okhttp
PleaSure乐事1 小时前
【Node.js】内置模块FileSystem的保姆级入门讲解
javascript·node.js·es6·filesystem
雷特IT1 小时前
Uncaught TypeError: 0 is not a function的解决方法
前端·javascript