ค้นหาเว็บไซต์

javascript - เงื่อนไขการแข่งขันเมื่อต้องใช้ฟังก์ชัน _.once ของ Lodash


ตัวอย่างเช่น พิจารณาโค้ดต่อไปนี้:

const fetch = _.once(myRealFetch)
const queue = new PQueue({concurrency: 1000});

queue.add(function() {
  const result = fetch()
  // Rest of the code ...
})
queue.add(function() {
  const result = fetch()
  // Rest of the code ...
})

await queue.start().onIdle(); 

มันประกาศฟังก์ชัน fetch ซึ่งถูกเมมโมไทซ์ให้ทำงานเพียงครั้งเดียวโดยใช้ฟังก์ชัน _.once ของ Lodash

จากนั้นฉันจึงสร้าง PQueue ต่อ เพิ่มฟังก์ชัน 2 ฟังก์ชันที่เรียกใช้ fetch เข้าไปในคิว และรันมัน

ตอนนี้สมมติว่า myRealFetch ใช้เวลา 5 วินาทีในการทำงาน เนื่องจากฉันเรียกมันด้วย concurrency > 1 นั่นหมายความว่าหนึ่งในสองฟังก์ชันจะเรียกใช้งานมันก่อน และมีโอกาสสูงว่าฟังก์ชันที่สองก็จะเรียกใช้งานมันด้วยเช่นกัน

ในตอนนี้ การเรียกใช้งานครั้งแรกยังไม่เสร็จ ดังนั้นสิ่งที่จะถูกส่งกลับในการเรียกใช้งานครั้งที่สองคืออะไร?

มีวิธีในตัวเพื่อจัดการกับเรื่องนั้นหรือไม่?

คำตอบ #1

คุณสามารถทดสอบสิ่งนี้ด้วยตัวเองด้วยบางสิ่งเช่นโค้ดด้านล่าง นี่คือ JSFiddle ที่คุณสามารถเล่นเพื่อดูว่าจะเกิดอะไรขึ้น

ฉันได้เพิ่มบันทึกบางส่วนพร้อมเวลาประทับ และยังได้เพิ่มรายการอื่นลงในคิวหลังจากงาน 2 งานแรกเสร็จสิ้น (ประมาณ 6s)

import { once } from 'https://esm.run/lodash-es';
import PQueue from 'https://esm.run/p-queue';
import moment from 'https://esm.run/moment';

const time = moment()

function logWithTime(msg) {
  console.log(`[T + ${moment().diff(time, 'ms')}ms]`, msg)
}

const wait = (t) => async () => new Promise((resolve) => {
  logWithTime(`Waiting for ${t / 1000}s...`);

  setTimeout(() => {
    logWithTime(`Done - Waited for ${t / 1000}s`);

    resolve('some result');
  }, t);
});

const fetch = once(wait(5000))
const queue = new PQueue({ concurrency: 1000 });

queue.add(async function() {
  logWithTime('adding 1')

  const result = await fetch()
  logWithTime('add1 - result')
  logWithTime(result)
})

queue.add(async function() {
  logWithTime('adding 2')

  const result = await fetch()
  logWithTime('add2 - result')
  logWithTime(result)
})

setTimeout(() => {
  logWithTime('adding 3')

  queue.add(async function() {
    const result = await fetch()
    logWithTime('add3 - result')
    logWithTime(result)
  })
}, 6000)

(async () => {
  await queue.start().onIdle();
})()

ผลลัพธ์ที่เกิดจากสิ่งนี้จะเป็น...

[T + 0ms] adding 1
[T + 0ms] Waiting for 5s...
[T + 0ms] adding 2
[T + 5001ms] Done - Waited for 5s
[T + 5002ms] add1 - result
[T + 5002ms] some result
[T + 5002ms] add2 - result
[T + 5002ms] some result
[T + 6002ms] adding 3
[T + 6002ms] add3 - result
[T + 6002ms] some result

คุณจะเห็นว่าคู่แรก 2 คู่ถูกแก้ไขประมาณ 5 วินาทีเกือบจะทันทีหลังจากกัน ดังนั้น fetch จึงถูกเรียกเพียง ครั้งเดียว สิ่งนี้ยังได้รับการยืนยันจากบันทึกภายในฟังก์ชัน wait ที่ถูกเรียกเพียงครั้งเดียวเช่นกัน

จากนั้นงานที่ 3 ซึ่งถูกเพิ่มหลังจากงานสองงานแรกได้รับการแก้ไขแล้ว จะถูกส่งกลับทันทีโดยไม่เรียกฟังก์ชัน fetch (เช่น wait) ดังนั้น _.once จะถูกใช้งานและการเรียกใช้ฟังก์ชัน fetch อื่นๆ จะถูกส่งกลับทันทีด้วยผลลัพธ์ที่จำค่าเดิมไว้