우리 모두 거기에 가본 적이 있어요. 우리는 각 항목에 대해 일종의 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; };
이 예제를 작성한 후 샤워를 하고 싶습니다... 동일한 배열(또는 지저분한 코드)의 엄청나게 많은 중복은 말할 것도 없고; 이는 임의의 시간 초과를 설정하여 실행 속도를 인위적으로 제한하는 것입니다
여기서 좋은 대답은 최대 동시성에 공간이 있을 때만 Promise를 생성하는 동시성 제한기를 만드는 것입니다. 다음과 유사한 것:
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에 대해 며칠 동안 심층 분석한 후 컨트롤러, 맵, 세트, 약한 맵 및 세트를 중단하십시오. 비동기화를 만들었습니다
Asyncrify를 사용한 내 목표는 간단했습니다. 또 다른 비동기 대기열을 만듭니다. 그러나 외부 종속성이 없으며 가능한 한 리소스가 가볍습니다.
대기열에 기능을 추가하고 최대 동시성을 설정할 수 있어야 했습니다. 시간 초과를 설정 및 처리하고 기하급수적으로 감소하는 재시도를 활성화 및 비활성화합니다.
그럼 묻지 않은 기술 문제는 무엇이었나요?
약속을 배우세요 아무리 강조해도 지나치지 않습니다.
내가 직면한 첫 번째 문제 중 하나는 약속 실행이 어떻게 작동하는지 이해하지 못했다는 것입니다. 첫 번째 구현은 다음과 같습니다.
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를 사용하여 "최대 동시" 약속을 동시에 실행하고 있습니다.
하지만 이는 첫 번째 Promise가 해결된 후에만 계속됩니다. 나머지는 무시됩니다. 그런 다음 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를 훨씬 더 잘 추적할 뿐만 아니라 사용자가 원하는 방식으로 오류와 해결 방법을 처리할 수도 있습니다.
중단 컨트롤러를 사용하세요 제가 자주 보는 큰 실수 중 하나는 약속이 초기화된 후 더 이상 필요하지 않을 때 사람들이 중단 컨트롤러를 사용하지 않는다는 것입니다. 나도 이것을했다.
처음에는 타임아웃을 하기 위해 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; };
상상하시는 대로죠. 약속은 시간 초과 후에도 계속 실행됩니다. 그냥 무시됐네요. 이것은 대기열을 구현한 첫 번째 실수와 많이 비슷해 보이지 않습니까?
중단 컨트롤러에 대한 나의 유일한 경험은 반응에 불과했기 때문에 나는 중단 컨트롤러에 대해 약간의 연구를 수행했습니다.
AbortSignal.timeout!! 이게 바로 제가 하고 싶었던 일이에요!
내 코드에 대한 유일한 업데이트는 한 줄이었습니다
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); } }); } }
그럼 비동기화는 어떻게 사용하나요?
정말 쉽습니다. 먼저 대기열을 만듭니다.
#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); }); }
대기열은 기본적으로 시간 초과나 폐기가 없으며 최대 동시 실행도 없습니다.
생성자에 구성 obj를 제공할 수도 있습니다.
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 중국어 웹사이트의 기타 관련 기사를 참조하세요!