wlasna implementacja metody Promise.all

wlasna implementacja metody Promise.all
MC
  • Rejestracja:ponad 6 lat
  • Ostatnio:3 minuty
  • Postów:33
0

Siema! Mam pewien problem, ucze sie typescript i postanowilem w ramach treningu zaimplementowac metode Promise.all. O ile logika w tym zagadnieniu jest juz dla mnie zrozumiala, to problem mam z uzyskanie w pelni funkcjonalnosci mojej metody. Nie wiem jak zabrac sie za to aby moja metoda zwracala rozne typy wartosci wynikow w tablicy. W JS mi to dziala jak powinno bo nie ma tam silnego typowania. Czy jest jakis latwy sposob aby zaimplemenowac moje rozwiazanie. (myslalem nad przeciazaniem) ale nie wiem czy to jest "czyste" rozwizanie. Ewentualnie czy ktos by mogl mnie naprowadzic na jakies materialy z zaawansowanym typescriptem. Pozdrawiam.

Kopiuj
const promiseAll = <T>(arrayOfPromise: Promise<T>[]): Promise<T[]> => {
    const isArrayOfPromisesEmpty = arrayOfPromise.length === 0;
    if (isArrayOfPromisesEmpty) 
     return Promise.resolve([]);
  
    const resolvedPromises: T[] = [];
    let resolvedPromisesCounter = 0;
    return new Promise((resolve, reject) => {
      for (const [index, promise] of arrayOfPromise.entries()) {
        promise
          .then((result) => {
            resolvedPromises[index] = result;
            resolvedPromisesCounter++;
  
            const hasResolvedAllPromises = resolvedPromisesCounter === arrayOfPromise.length
  
            if (hasResolvedAllPromises) {
              resolve(resolvedPromises);
            }
          })
          .catch((error) => {
            reject(error);
          });
      }
    });
  };
  
  const promise = new Promise<string>((resolve,recjet) =>
     setTimeout(() => {
      resolve("GooD")
   }, 1000));
  const promise2 = new Promise<string>((resolve,recjet) =>
    setTimeout(() => {
     resolve("World")
  }, 1000));
  const promise3= new Promise<string>((resolve, reject) =>{
     setTimeout(() =>{
        resolve("!")
     },200)
  
  }
  );
  
  promiseAll([promise, promise2, promise3])
    .then((results) => console.log("Success:", results)) 
    .catch((error) => console.error("Error:", error));
obscurity
  • Rejestracja:około 6 lat
  • Ostatnio:minuta
0

Możesz podejrzeć nagłówki istniejącego Promise.all, w tym przypadku wygląda tak:

Kopiuj
const promiseAll = <T extends readonly unknown[] | []>(arrayOfPromise: T): Promise<{ -readonly [P in keyof T]: Awaited<T[P]>; }> => {

i korzysta z wbudowanego typu Awaited który wygląda tak:

Kopiuj
/**
 * Recursively unwraps the "awaited type" of a type. Non-promise "thenables" should resolve to `never`. This emulates the behavior of `await`.
 */
type Awaited<T> = T extends null | undefined ? T : // special case for `null | undefined` when not in `--strictNullChecks` mode
    T extends object & { then(onfulfilled: infer F, ...args: infer _): any; } ? // `await` only unwraps object types with a callable `then`. Non-object types are not unwrapped
        F extends ((value: infer V, ...args: infer _) => any) ? // if the argument to `then` is callable, extracts the first argument
            Awaited<V> : // recursively unwrap the value
        never : // the argument to `then` was not callable
    T; // non-object or non-thenable

więc w tym przypadku akurat masz dużo ułatwione.

Typescript jest dość trudny jak chcesz utrzymać typy w każdym przypadku - w wielu miejscach rozwiązanie staje się szybko overengineered i lepiej skorzystać z uproszczenia idąc na kompromis z uproszczonymi typami niż z rozwiązania którego nikt w zespole nie będzie w stanie zrozumieć.

Jeśli chodzi o zaawansowany typescript to polecam tego gościa na youtube: @Typed-Rocks
Potrafi robić niesamowite rzeczy z typami, ale nie polecam używać tych rozwiązań w zwykłych projektach.


"A car won't take your job, another horse driving a car will." - Horse influencer, 1910
edytowany 1x, ostatnio: obscurity
MC
  • Rejestracja:ponad 6 lat
  • Ostatnio:3 minuty
  • Postów:33
0
Kopiuj
const promiseAll = <T extends unknown[]>(
    arrayOfPromises: [...T]
  ): Promise<{ [K in keyof T]: Awaited<T[K]> }> => {
    const isArrayEmpty = arrayOfPromises.length === 0;
    if (isArrayEmpty) return Promise.resolve(arrayOfPromises as { [K in keyof T]: Awaited<T[K]> });
  
    return new Promise((resolve, reject) => {
      const quantityOfElementsInInputArray = arrayOfPromises.length;
      const results = new Array(quantityOfElementsInInputArray) as { [K in keyof T]: Awaited<T[K]> };
      let promiseCounter = 0;
  
      for (const [index, promise] of arrayOfPromises.entries()) {
        const currentIndex = index;
        Promise.resolve(promise)
          .then((result) => {
            results[currentIndex] = result as Awaited<T[typeof index]>;
            promiseCounter++;
  
            if (promiseCounter === quantityOfElementsInInputArray) {
              resolve(results);
            }
          })
          .catch(reject);
      }
    });
  };



promiseAll(arrayOfPromises).then(console.log)
Promise.all(arrayOfPromises)

to jest co wydaje mi sie chcialem osiagnac poniewaz gdy podgladam zwracane typy z orginalnym Promise.all wyglada to podobnie. Pytanie czy moge jakos pozbyc sie tego rzutowania. (slyszalem ze powinnismy takiego wymuszania typow unikac) ?

edytowany 1x, ostatnio: mcbrewa
kasiaKasia
  • Rejestracja:ponad 14 lat
  • Ostatnio:około 2 miesiące
  • Postów:259
0

Moim zdaniem zadeklarowanie type lub interface będzie łatwiejszym zastosowaniem. Używasz typów generycznych, one są stosowane aby określony typ był reużywalny. Z Twojego postu wynika, że chcesz określony typ zastosować. Poniżej to przykład CustomResult możesz dostosować do swoich potrzeb

Kopiuj

// Tu zrobisz typ w jakiej postacji chcesz aby Promise został mapowany 

type CustomResult = {
  value: number;
};

function myPromiseAll(promises: (number | Promise<number>)[]): Promise<CustomResult[]> {
  return new Promise((resolve, reject) => {
    if (!Array.isArray(promises)) {
      return reject(new TypeError("Input must be an array"));
    }
//  zakladasz ze zwracane wartosci to liczby z Promise
    const results: number[] = [];
    let completed = 0;

    promises.forEach((promise, index) => {

      Promise.resolve(promise)
        .then((value) => {
          results[index] = value;
          completed++;

          if (completed === promises.length) {
            // zastosowanie typu CustomResult - może być inny 
            const customResult: CustomResult[] = results.map((result: number) => ({ value: result }));
            resolve(customResult);
          }
        })
        .catch((error) => {
          reject(error);
        });
    });

    if (promises.length === 0) {
      resolve([]);
    }
  });
}

const promises = [
  Promise.resolve(10),
  20,
  Promise.resolve(30),
];

myPromiseAll(promises).then((results) => {
  console.log(results);
});


edytowany 3x, ostatnio: kasiaKasia
MC
  • Rejestracja:ponad 6 lat
  • Ostatnio:3 minuty
  • Postów:33
0

ale tutaj chodzi o to zeby wlasnie zaimplementowac metode ktora dziala tak samo jak Promise.all wiec to z uzyciem tylko generykow jest za proste po ogranicza mnie do sprawdzenia wczesniej jakie typy wchodza do tablicy.

kasiaKasia
  • Rejestracja:ponad 14 lat
  • Ostatnio:około 2 miesiące
  • Postów:259
0

Moim zdaniem:
tak, rzutowania nie należy stosować. Powodem jest to, że powinno to być to ustalone wcześniej.

Generyczne typy mają zastosowanie w przypadku kiedy jeszcze nie ma ustalonego typu.

Nie wiem jak zabrac sie za to aby moja metoda zwracala rozne typy wartosci wynikow w tablicy

To Ci działa, ale wtedy już nie jest to ścisłe typowanie, bo tego typu nie ustaliłeś.

Za proponuję ustalić kilka typów (najlepiej małe, rozszerzalne, podstawowe (czyli jak klasa do powiadomień, to w niej nie robimy innych metod, niż wyślij), nienadpisywane ) i za pomocą | lub & połączyć w jeden zgodnie z wymaganiami.


edytowany 1x, ostatnio: kasiaKasia

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.