首頁 >web前端 >js教程 >Netflix 面試問題如何變成我的第一個 NPM 包

Netflix 面試問題如何變成我的第一個 NPM 包

Linda Hamilton
Linda Hamilton原創
2024-12-28 01:04:09795瀏覽

How a Netflix Interview question turned into my first NPM package

不理解 Promise 的問題

我們都經歷過。我們有大量數據,需要為每個條目發出某種 api 請求。假設它是不同場地的 id 數組,您需要從中取得場地提供者並傳回該提供者數組。我們建立了一個新函數來發出這些請求...

  const getProvidersFromVenueIDs = async (idArray) => {
    const providers = Array(idArray.length);
    for (let i = 0; i >= idArray.length - 1; i++) {
      const res = await fetch(
        `https://venues_for_me.org/venueid=${idArray[i]}`
        );
      const venue = res.data;
      providers[i] = venue.provider;
    }
    return providers;
  };

哎呀,你剛剛根據你的所有請求關閉了 8 年前的遺留伺服器......
我覺得我們都曾在某些時候犯過這樣的錯誤,一個解決方案是在一批請求之間設定幾毫秒的超時...

  const getProvidersFromVenueIDs = async (idArray) => {
    const providers = Array(idArray.length);
    const batchSize = 50;
    for (let i = 0; i >= idArray.length - 1; i++) {
      const batchToExecute = Array(batchSize);
      for (let y = 1; i >= batchSize; i++) {
        batchToExecute[i] = fetch(
          `https://venues_for_me.org/venue?id=${idArray[i]}`,
        );
        await (async () => setTimeout(() => {}, 200))();
      }
      const responses = await Promise.all(batchToExecute);
      responses.forEach((venue) => {
        providers[i] = venue.provider;
      });
    }
    return providers;
  };

寫完這個例子我就想洗個澡……更不用說相同數組的絕對瘋狂的重複(或者凌亂的代碼);這是通過設置任意超時人為限制您的執行速度

這裡一個好的答案是創建一個並發限制器,僅當最大並發數有空間時才創建承諾。類似:

  getProvidersFromVenueIDs = async (idArray) => {
    const providers = Array(idArray.length);
    const batchSize = 50;
    for (let i = 0; i >= idArray.length - 1; i++) {
      const batchToExecute = Array(batchSize);
      for (let y = 1; i >= batchSize; i++) {
        batchToExecute[i] = fetch(
          `https://venues_for_me.org/venue?id=${idArray[i]}`,
        );
        await (async () => setTimeout(() => {}, 200))();
      }
      const responses = await Promise.all(batchToExecute);
      responses.forEach((venue) => {
        providers[i] = venue.provider;
      });
    }
    return providers;
  };

如您所見,為了不失去承諾,您需要實現某種佇列來保留要發出的請求的積壓。這篇文章的標題來了。

鄧寧克魯格

我正在觀看 The Primagen 的一段視頻,一個特定的部分引起了我的注意。在 Netflix 訪談中,他最喜歡問的問題之一是讓受訪者創建一個非同步隊列,以及執行 Promise 的最大並發數。
這聽起來和我遇到的上述問題一模一樣!

這個面試問題有多個層次。佇列實作後,實作錯誤重試。
我花了一個下午的時間來應對這個挑戰,很快我就發現我有技能問題。事實證明,我並不像我想像的那樣了解 Promise。
在花了幾天時間深入研究承諾之後,中止控制器、地圖、集合、弱地圖和集合。我創建了 Asyncrify

使用 Asyncrify 我的目標很簡單。建立另一個非同步隊列。但沒有外部依賴,資源盡可能輕量。
它需要能夠向隊列添加函數,並設定最大並發度。設定和處理逾時並啟用、停用指數下降的重試。

是技能問題

那我聽到你沒有問的那些技能問題是什麼?

了解你的承諾這一點我怎麼強調都不為過。
我遇到的第一個問題是我不明白 Promise 的執行是如何運作的。我的第一個實作看起來像這樣:

  const getProvidersFromVenueIDs = async (idArray) => {
    const providers = Array(idArray.length);
    for (let i = 0; i >= idArray.length - 1; i++) {
      const res = await fetch(
        `https://venues_for_me.org/venueid=${idArray[i]}`
        );
      const venue = res.data;
      providers[i] = venue.provider;
    }
    return providers;
  };

我確信您立即看到了問題。我正在使用 Promise.race 同時執行我的「最大並發」承諾。
但這只有在第一個承諾兌現後才會繼續。其餘的被忽略。然後我再添加 1 個並再次執行它們。
我必須回到基礎。
解決方案是改用 .then 和 .catch 並僅噹噹前運行的部分中有空位時才運行該函數。

  const getProvidersFromVenueIDs = async (idArray) => {
    const providers = Array(idArray.length);
    const batchSize = 50;
    for (let i = 0; i >= idArray.length - 1; i++) {
      const batchToExecute = Array(batchSize);
      for (let y = 1; i >= batchSize; i++) {
        batchToExecute[i] = fetch(
          `https://venues_for_me.org/venue?id=${idArray[i]}`,
        );
        await (async () => setTimeout(() => {}, 200))();
      }
      const responses = await Promise.all(batchToExecute);
      responses.forEach((venue) => {
        providers[i] = venue.provider;
      });
    }
    return providers;
  };

現在我們可以更好地追蹤並發承諾,但我們也使用戶能夠按照他們想要的方式處理錯誤和解決方案。

請使用中止控制器我經常看到的一個大錯誤是,當承諾在初始化後不再需要時,人們不會使用中止控制器。我也做了這個。
一開始,為了實現超時,我使用了Promise.race

  getProvidersFromVenueIDs = async (idArray) => {
    const providers = Array(idArray.length);
    const batchSize = 50;
    for (let i = 0; i >= idArray.length - 1; i++) {
      const batchToExecute = Array(batchSize);
      for (let y = 1; i >= batchSize; i++) {
        batchToExecute[i] = fetch(
          `https://venues_for_me.org/venue?id=${idArray[i]}`,
        );
        await (async () => setTimeout(() => {}, 200))();
      }
      const responses = await Promise.all(batchToExecute);
      responses.forEach((venue) => {
        providers[i] = venue.provider;
      });
    }
    return providers;
  };

正如你想像的那樣。逾時後承諾仍會執行。它只是被忽略了。這看起來很像我在實作佇列時犯的第一個錯誤,不是嗎?
我對中止控制器做了一些研究,因為我對它們的唯一經驗只是反應。
中止訊號.超時!這正是我想做的事!
我的程式碼唯一的更新是 1 行

 async #runTasksRecursively() {
        await this.#runAsync();
        if (this.#queue.size === 0 && this.#retries.length === 0) {
            return;
        }

        this.#addToPromiseBlock();
    }

    async #runAsync() {
        if (!this.#runningBlock.every((item) => item === undefined)) {
            await Promise.race(this.#runningBlock);
        }
    }

    #addToPromiseBlock() {
        const emptyspot = this.#getEmptySpot();
        if (this.#retries.length > 0 && !this.#lastRunWasError) {
            console.log(this.#retries);
            if (this.#errorsToInject.size > 0) {
                const task = this.#popInSet(this.#errorsToInject);
                if (this.#queue.size !== 0) {
                    this.#lastRunWasError = true;
                }
                this.#assignPromisToExecutionArray(task, emptyspot);
            }
        } else {
            const task = this.#popInSet(this.#queue);
            this.#lastRunWasError = false;
            this.#assignPromisToExecutionArray(task, emptyspot);
        }
    }

哇,太簡單了!但現在套件的用戶需要創建樣板才能使用超時功能。無需害怕!我是為了你才這麼做的!

  add(fn, callback, errCallback) {
    if (this.#maxConcurrency !== 0 && this.#running >= this.#maxConcurrency) {
      this.#queue.add(fn);
    } else {
      this.#running++;
      fn()
        .then(callback)
        .catch(errCallback)
        .finally(() => {
          this.#running--;
          if (this.#queue.size > 0) {
            const nextPromise = this.#queue.values().next().value;
            this.#queue.delete(nextPromise);
            this.add(nextPromise, callback, errorCallback);
          }
        });
    }
  }

另一個微型 NPM 包

那麼要如何使用 Asyncrify 呢?
嗯,這真的很容易。我們首先建立隊列。

  #promiseBuilder(fn) {
        const promise = new Array(this.#promiseTimeout > 0 ? 2 : 1);
        promise[0] = fn();

        if (this.#promiseTimeout > 0) {
            promise[1] = this.#timeoutHandler();
        }
        return promise;
    }
 #promiseRunner(fn, callback) {
        const promise = this.#promiseBuilder(fn);
        Promise.race(promise)
            .then((res) => {
                callback(res, null);
            })
            .catch((err) => {
                this.#errorHandler(err, fn, callback);
            })
            .finally(() => {
                this.#running--;
                this.#runPromiseFromQueue(callback);
            });
    }

佇列預設沒有逾時或退出,也沒有最大並發數。
您也可以向建構函數提供配置物件。

     const promise = fn(
      this.#timeout > 0 ? AbortSignal.timeout(this.#timeout) : null,
    );

要將 Promise 加入到佇列中,您必須包裝在傳回它的函數中。

export const abortHandler = (signal, reject) => {
  if (signal.aborted) {
    return reject(new Error("Aborted"));
  }
  const abortHandler = () => {
    reject(new Error("Aborted"));
    signal.removeEventListener("abort", abortHandler);
  };
  signal.addEventListener("abort", abortHandler);
};

記得加入中止處理程序才能使用逾時功能!

然後您需要做的就是將函數以及回調和錯誤回調傳遞給 add 方法

import Queue from 'Asyncrify'

const queue = new Queue()

添加就是這樣!想添加多少就添加多少,想快多少就添加多少,一次只會運行 3 個,直到全部完成為止!

在創建這個包的過程中我學到了很多。可以說我很久以前就該知道的事。這就是我寫這篇文章的原因。我希望你們看到我犯過的那些可以說是愚蠢的錯誤,並且受到鼓勵去犯愚蠢的錯誤並從中學習。而不是在事情發生時感到尷尬並躲開。

出去寫一篇文章。創建一個微型包,每週從機器人下載 10 次。你最終會學到你從來不知道自己需要的東西

以上是Netflix 面試問題如何變成我的第一個 NPM 包的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn