V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐关注
Meteor
JSLint - a JavaScript code quality tool
jsFiddle
D3.js
WebStorm
推荐书目
JavaScript 权威指南第 5 版
Closure: The Definitive Guide
nbin2008
V2EX  ›  JavaScript

请教大家一个问题, js,异步执行

  •  
  •   nbin2008 · 120 天前 · 2150 次点击
    这是一个创建于 120 天前的主题,其中的信息可能已经有所发展或是发生改变。
    运行环境:nodejs
    需求:提供一个方法 getD ,此方法内调用 ajax 接口获取数据,然后对外输出
    1 、异步调用,比如会同时执行 30 次 getD
    2 、当第一次调用 getD ,方法内没有数据,会从 ajax 拿数据,每次拿 10 条,等待中,当拿到数据后,用一条对外提供
    3 、当第二次及后续调用 getD
    a 如果有前面(第一次)正在 ajax 中,则等待,当 ajax 请求拿到数据后,第二次及后续的用拿到的数据
    b 如果前面没有 ajax 中,则直接使用数据
    4 、当 ajax 拿到的数据用完后,重复 2-3 ,但 ajax 每次请求间隔,须相隔 2s

    不知道咋写,写出的也不满足,谢谢大家
    14 条回复    2024-07-25 15:31:05 +08:00
    InDom
        1
    InDom  
       120 天前
    好像没啥难度?

    做一个 对象,首先需要一 or 两个锁,然后需要一 or 两个队列。

    发起 ajax 需要过一下锁,ajax 发出前锁定,ajax 成功后,锁设置过期时间 2s 。

    getD 先检查队列是否能取到,成功就返回,失败就 ajax , 如果发出就等成功,失败就把回调推入等待队列。

    ajax 成功后挨个讲结果回调给等待队列。
    shiny
        2
    shiny  
       120 天前
    这里要考虑一个问题,只有一个进程还是会有多个进程。如果是单进程的,直接用变量就能实现;多进程需要借助其他服务。

    PS:你的描述够详细了,ChatGPT 写出来的就已经八九不离十了。
    zbinlin
        3
    zbinlin  
       120 天前
    用 async generator 来写试试
    nbin2008
        4
    nbin2008  
    OP
       120 天前
    @InDom 谢谢提供的思路,我想想
    @shiny 谢谢,单进程
    vampuke
        5
    vampuke  
       120 天前
    let pending;
    const getId = async() => {
    if (pending) return pending;
    pending = new Promise((resolve) => {
    setTimeout(() => {
    resolve('id');
    setTimeout(() => {
    pending = null;
    }, 2000);
    }, 1000);
    });
    return pending;
    }
    Lhcfl
        6
    Lhcfl  
       120 天前
    不考虑多线程:
    function makeGetD() {
    let datas = [];
    let promise = null;
    let canNext = true;
    const getD = async () => {
    if (datas.length > 0) return datas.pop();
    if (promise == null) {
    if (!canNext) return;
    canNext = false;
    setTimeout(() => (canNext = true), 2000);
    promise = new Promise(res => setTimeout(() => {
    console.log("i'm ajax"); res([1,2,3,4,5,6,7,8,9,10]);
    }, 300));
    }
    datas = await promise;
    promise = null;
    return await getD();
    }
    return getD;
    }

    getD = makeGetD();
    Curtion
        7
    Curtion  
       120 天前
    nbin2008
        8
    nbin2008  
    OP
       120 天前
    感谢大家帮忙,没来得及看,用 2 楼的方法解决了

    const axios = require('axios');

    let dataQueue = [];
    let isFetching = false;

    async function fetchData() {
    if (isFetching) return;
    isFetching = true;
    try {
    const response = await axios.get('https://example.com/api/data'); // 替换为实际的 API 地址
    dataQueue = response.data.slice(0, 10); // 假设每次获取 10 条数据
    } catch (error) {
    console.error('Error fetching data:', error);
    } finally {
    isFetching = false;
    }
    }

    async function getD() {
    while (dataQueue.length === 0) {
    if (!isFetching) {
    await fetchData();
    }
    await new Promise(resolve => setTimeout(resolve, 100)); // 等待数据获取完成
    }
    return dataQueue.shift();
    }

    // 示例:同时执行 30 次 getD
    (async () => {
    const promises = Array.from({ length: 30 }, () => getD());
    const results = await Promise.all(promises);
    console.log(results);
    })();
    nbin2008
        9
    nbin2008  
    OP
       120 天前
    @Lhcfl if (!canNext) return; 这里返回,就没有数据了
    @Curtion 55 行调用,这里不是并发,整体代码很值得学习
    谢谢楼上 v 友们的解答
    Projection
        10
    Projection  
       119 天前
    你说的这个用 Streams API 很容易实现:

    ```js
    import { setTimeout } from "node:timers/promises";

    let id = 0;

    async function fetchData() {
    console.log("fetchData()");
    await setTimeout(Math.random() * 500);
    return Array(10)
    .fill(null)
    .map(() => ({ id: ++id }));
    }

    const stream = new ReadableStream(
    {
    async pull(controller) {
    const data = await fetchData();
    for (const a of data) {
    controller.enqueue(a);
    }
    },
    },
    new CountQueuingStrategy({ highWaterMark: 0 }), // fully lazy
    ).values();

    async function getD() {
    return (await stream.next()).value;
    }

    for (let i = 0; i < 30; i++) {
    console.log("data", await getD());
    }
    ```

    输出结果:

    fetchData()
    data { id: 1 }
    data { id: 2 }
    data { id: 3 }
    data { id: 4 }
    data { id: 5 }
    data { id: 6 }
    data { id: 7 }
    data { id: 8 }
    data { id: 9 }
    data { id: 10 }
    fetchData()
    data { id: 11 }
    data { id: 12 }
    data { id: 13 }
    data { id: 14 }
    data { id: 15 }
    data { id: 16 }
    data { id: 17 }
    data { id: 18 }
    data { id: 19 }
    data { id: 20 }
    fetchData()
    data { id: 21 }
    data { id: 22 }
    data { id: 23 }
    data { id: 24 }
    data { id: 25 }
    data { id: 26 }
    data { id: 27 }
    data { id: 28 }
    data { id: 29 }
    data { id: 30 }

    这里为了定义你所需要 getD() 显得把代码搞复杂了一点,而实际上你可以根据情况使用 for await... of 。

    你从 LLM 获得的代码有轮询的部分,所以强烈不推荐:

    await new Promise(resolve => setTimeout(resolve, 100)); // 等待数据获取完成
    jifengg
        11
    jifengg  
       119 天前
    综合看下来,我自己写的话应该也和 @Curtion 差不多。
    你不需要一个 isFetching 来表示是否正在异步获取。如果不是你要求两次间隔 2s ,就根本不需要 setTimeout

    当然,实际上你应该还要考虑 接口返回数据量是 0 的情况。
    nbin2008
        12
    nbin2008  
    OP
       119 天前
    @jifengg isFetching 作用,保证当时请求,始终只有一次
    nbin2008
        13
    nbin2008  
    OP
       119 天前
    @Projection 感谢,看的不是很懂,api 生疏
    DOLLOR
        14
    DOLLOR  
       119 天前
    上面某人启发了我,Async Generator 就很容易实现,甚至不需要多余的全局变量,关键代码参见图中的 async function*

    https://imgur.com/3n12UuC
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1932 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 16:18 · PVG 00:18 · LAX 08:18 · JFK 11:18
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.