javascript - לאסוף מספר קריאות ואז להפריד את התוצאה עם Promise


כרגע יש לי הרבה קריאות זהות בו-זמנית לשרת האחורי שלי, השונות רק בשדה מזהה:

getData(1).then(...) // Each from a React component in a UI framework, so difficult to aggregate here
getData(2).then(...)
getData(3).then(...)

// creates n HTTP requests... inefficient
function getData(id: number): Promise<Data> {
  return backend.getData(id);
}

זה מבזבז זמן מכיוון שאני מבצע יותר שיחות. אני רוצה לשמור על הקריאות שלי ל-getData(), אך ואז לאגד אותן לקריאה אחת ל-getDatas() לשרת שלי, ולאחר מכן להחזיר את כל התוצאות למתקשרים. יש לי יותר שליטה על השרת שלי מאשר על מסגרת ה-UI, כך שאוכל בקלות להוסיף קריאה ל-getDatas() עליו. השאלה היא איך "למזג" את הקריאות ב-JS לקריאה אחת לשרת, ואז "לפצל" את התוצאה להבטחות של המתקשרים.

const cache = Map<number, Promise<Data>>()
let requestedIds = []
let timeout = null;

// creates just 1 http request (per 100ms)... efficient!
function getData(id: number): Promise<Data> {
  if (cache.has(id)) {
    return cache;
  }
  
  requestedIds.push(id)
  if (timeout == null) {
    timeout = setTimeout(() => {
      backend.getDatas(requestedIds).then((datas: Data[]) => {
        // TODO: somehow populate many different promises in cache??? but how?
        requestedIds = []
        timeout = null
      }
    }, 100)
  }
  return ???
}

ב-Java הייתי יוצר Map<int, CompletableFuture> ובסיום בקשת ה-backend שלי, הייתי מחפש את ה-CompletableFuture ומבצע עליו complete(data). אבל אני חושב שב-JS אי אפשר ליצור Promise בלי להעביר תוצאה מפורשת.

האם אני יכול לעשות את זה ב-JS עם Promises?

תשובה #1

לא ברור לגמרי איך המטרה הסופית שלך נראית. אני מניח שאתה יכול לחזור על הקריאות שלך לפי הצורך; אולי משהו כזה:

for (let x in cache){
   if (x.has(id))
      return x;
   } 

//OR

for (let x=0; x<id.length;x++){
  getData(id[x])
}



יכול לעבוד. ייתכן שתוכל להוסיף שיטת תזמון לתערובת אם יהיה צורך.

לא בטוח ממה מורכב הבאקאנד שלך, אבל אני כן יודע ש-GraphQL הוא מערכת טובה לביצוע מספר קריאות.

ייתכן שבסופו של דבר עדיף לטפל בכולם בבקשה אחת, במקום במספר קריאות.

תשובה #2

המטמון יכול להיות אובייקט רגיל הממפה מזהים לפונקציות פתרון הבטחות ולבטחה שאליהן הם שייכים.

// cache maps ids to { resolve, reject, promise, requested }
// resolve and reject belong to the promise, requested is a bool for bookkeeping
const cache = {};

ייתכן שתצטרך להפעיל רק פעם אחת, אבל כאן אני מציע להשתמש ב-setInterval לבדוק באופן קבוע את המטמון עבור בקשות שלא נפתרו:

// keep the return value, and stop polling with clearInterval()
// if you really only need one batch, change setInterval to setTimeout
function startGetBatch() {
  return setInterval(getBatch, 100);
}

הלוגיקה העסקית קוראת רק ל-getData() שמספקת (ומתעדת) הבטחות, כך:

function getData(id) {
  if (cache[id]) return cache[id].promise;
  cache[id] = {};
  const promise = new Promise((resolve, reject) => {
    Object.assign(cache[id], { resolve, reject });
  });
  cache[id].promise = promise;
  cache[id].requested = false;
  return cache[id].promise;
}

על ידי שמירת ההבטחה יחד עם המפענח והמחותה, אנו גם מיישמים את המטמון, שכן ההבטחה המוסכמת תספק את הדבר שאליו היא הוסכמה דרך המתודה then() שלה.

getBatch() מבקש מהשרת באופן קבוצתי את מזהי getData() שעדיין לא נדרשו, ומקרא לפונקציות resolve/reject המתאימות:

function getBatch() {
  // for any
  const ids = [];
  Object.keys(cache).forEach(id => {
    if (!cache[id].requested) {
      cache[id].requested = true;
      ids.push(id);
    }
  });
  return backend.getDatas(ids).then(datas => {
    Object.keys(datas).forEach(id => {
      cache[id].resolve(datas[id]);
    })
  }).catch(error => {
    Object.keys(datas).forEach(id => {
      cache[id].reject(error);
      delete cache[id]; // so we can retry
    })
  })
}

צד המתקשר נראה כך:

// start polling 
const interval = startGetBatch();

// in the business logic
getData(5).then(result => console.log('the result of 5 is:', result));
getData(6).then(result => console.log('the result of 6 is:', result));

// sometime later...
getData(5).then(result => {
  // if the promise for an id has resolved, then-ing it still works, resolving again to the -- now cached -- result
  console.log('the result of 5 is:', result)
});

// later, whenever we're done 
// (no need for this if you change setInterval to setTimeout)
clearInterval(interval);