首页 > web前端 > js教程 > Netflix 面试问题如何变成我的第一个 NPM 包

Netflix 面试问题如何变成我的第一个 NPM 包

Linda Hamilton
发布: 2024-12-28 01:04:09
原创
792 人浏览过

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中文网其他相关文章!

来源:dev.to
本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
作者最新文章
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板