developer tip

루프를위한 JavaScript ES6 약속

copycodes 2020. 8. 21. 07:58
반응형

루프를위한 JavaScript ES6 약속


이 질문에 이미 답변이 있습니다.

for (let i = 0; i < 10; i++) {
    const promise = new Promise((resolve, reject) => {
        const timeout = Math.random() * 1000;
        setTimeout(() => {
            console.log(i);
        }, timeout);
    });

    // TODO: Chain this promise to the previous one (maybe without having it running?)
}

위의 내용은 다음과 같은 임의의 출력을 제공합니다.

6
9
4
8
5
1
7
2
3
0

작업은 간단합니다. 각 Promise는 다른 Promise 이후에만 실행되도록합니다 ( .then()).

어떤 이유로 나는 그것을 할 방법을 찾지 못했습니다.

생성기 함수 ( yield)를 시도하고 약속을 반환하는 간단한 함수를 시도했지만 하루가 끝나면 항상 동일한 문제 가 발생합니다 . 루프는 동기 입니다.

비동기 단순히 사용하십시오 async.series().

어떻게 해결합니까?


질문에서 이미 암시했듯이 코드는 모든 약속을 동기식으로 생성합니다. 대신 이전 항목이 해결 될 때만 만들어야합니다.

둘째,로 생성 된 각 프라 미스는 (또는 )에 new Promise대한 호출로 해결되어야합니다 . 타이머가 만료되면이 작업을 수행해야합니다. 그러면 해당 약속에 대한 모든 콜백 이 트리거됩니다 . 그리고 이러한 콜백 (또는 )은 체인을 구현하기 위해 필요합니다.resolverejectthenthenawait

이러한 구성 요소를 사용하여이 비동기 체인을 수행하는 몇 가지 방법이 있습니다.

  1. for즉시 해결하는 약속으로 시작 하는 루프

  2. Array#reduce그는 즉시 해결 약속으로 시작

  3. 자신을 해결 콜백으로 전달하는 함수 사용

  4. ECMAScript2017의 async/ await구문

  5. 제안 된 ECMAScript2020의 for await...of구문

아래에서 이러한 각 옵션에 대한 스 니펫과 설명을 참조하십시오.

1. 함께 for

당신은 할 수 있습니다 용도 for루프,하지만 당신은 그것을 실행하지 않도록해야 new Promise기적. 대신 초기 즉시 해결 약속을 만든 다음 이전 약속이 해결되면 새 약속을 연결합니다.

for (let i = 0, p = Promise.resolve(); i < 10; i++) {
    p = p.then(_ => new Promise(resolve =>
        setTimeout(function () {
            console.log(i);
            resolve();
        }, Math.random() * 1000)
    ));
}

2.와 함께 reduce

이것은 이전 전략에 대한보다 기능적인 접근 방식 일뿐입니다. 실행하려는 체인과 길이가 같은 배열을 만들고 즉시 해결 약속으로 시작합니다.

[...Array(10)].reduce( (p, _, i) => 
    p.then(_ => new Promise(resolve =>
        setTimeout(function () {
            console.log(i);
            resolve();
        }, Math.random() * 1000)
    ))
, Promise.resolve() );

이것은 약속에 사용될 데이터 있는 배열 이 실제로 있을 때 더 유용 할 것 입니다.

3. 해결 콜백으로 자신을 전달하는 함수

여기에서 함수를 만들고 즉시 호출합니다. 첫 번째 약속을 동기식으로 생성합니다. 해결되면 함수가 다시 호출됩니다.

(function loop(i) {
    if (i < 10) new Promise((resolve, reject) => {
        setTimeout( () => {
            console.log(i);
            resolve();
        }, Math.random() * 1000);
    }).then(loop.bind(null, i+1));
})(0);

이것은 이름이라는 함수를 생성 loop하고 코드의 맨 끝에 인수 0으로 즉시 호출되는 것을 볼 수 있습니다. 이것은 카운터이고 i 인수입니다. 이 함수는 카운터가 여전히 10 미만이면 새로운 약속을 생성하고 그렇지 않으면 연결이 중지됩니다.

에 대한 호출 은 함수를 다시 호출하는 콜백을 resolve()트리거 then합니다. loop.bind(null, i+1)그냥 다른 말입니다 _ => loop(i+1).

4. async/await

최신 JS 엔진 은 다음 구문을 지원합니다 .

(async function loop() {
    for (let i = 0; i < 10; i++) {
        await new Promise(resolve => setTimeout(resolve, Math.random() * 1000));
        console.log(i);
    }
})();

그것으로는, 이상하게 보일 수 있습니다 new Promise()호출이 비동기 적으로 실행되지만, 실제로는 async함수 반환 은 처음 실행할 때 await. 기다린 약속이 해결 될 때마다 함수의 실행중인 컨텍스트가 복원되고, await다음 항목을 만날 때까지 계속되고 루프가 끝날 때까지 계속됩니다.

타임 아웃을 기준으로 프라 미스를 반환하는 것은 일반적인 일이므로 이러한 프라 미스를 생성하기위한 별도의 함수를 만들 수 있습니다. 이것을 함수 약속 이라고 setTimeout합니다. 이 경우에는 . 코드의 가독성을 향상시킬 수 있습니다.

const delay = ms => new Promise(resolve => setTimeout(resolve, ms));

(async function loop() {
    for (let i = 0; i < 10; i++) {
        await delay(Math.random() * 1000);
        console.log(i);
    }
})();

5.와 함께 for await...of

최근에는 for await...of구문이 일부 JavaScript 엔진에 적용되었습니다. 이 경우 실제로 코드를 줄이지는 않지만 무작위 간격 체인의 정의를 실제 반복에서 분리 할 수 ​​있습니다.

const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
async function * randomDelays(count ,max) {
    for (let i = 0; i < count; i++) yield delay(Math.random() * max).then(() => i);
}

(async function loop() {
    for await (let i of randomDelays(10, 1000)) console.log(i);
})();


async/await이것을 위해 사용할 수 있습니다 . 더 설명하겠습니다.하지만 실제로는 아무것도 없습니다. 그것은 단지 규칙적인 for루프이지만 await약속을 만들기 전에 키워드를 추가했습니다.

내가 이것에 대해 좋아하는 점은 Promise가 코드 (또는 여기의 다른 답변) 포함과 같은 부작용을 갖는 대신 정상적인 값을 해결할 수 있다는 것입니다. 이를 통해 The Legend of Zelda : A Link to the Past 에서 빛의 세계 어둠의 세계 모두에 영향을 미칠 수있는 것과 같은 힘 을 얻을 수 있습니다. 즉, 약속 된 데이터를 사용할 수 있기 전 / 후에 데이터를 사용할 필요없이 쉽게 작업 할 수 있습니다. 깊이 중첩 된 함수, 기타 다루기 힘든 제어 구조 또는 어리석은 IIFE에 의존합니다 .

// where DarkWorld is in the scary, unknown future
// where LightWorld is the world we saved from Ganondorf
LightWorld ... await DarkWorld

그래서 다음과 같이 보일 것입니다 ...

const someProcedure = async n =>
  {
    for (let i = 0; i < n; i++) {
      const t = Math.random() * 1000
      const x = await new Promise(r => setTimeout(r, t, i))
      console.log (i, x)
    }
    return 'done'
  }

someProcedure(10).then(x => console.log(x)) // => Promise
// 0 0
// 1 1
// 2 2
// 3 3
// 4 4
// 5 5
// 6 6
// 7 7
// 8 8
// 9 9
// done

.then절차 내에서 귀찮은 호출 을 처리 할 필요가 없는지 알 겠습니까? 그리고 async키워드는 a Promise가 반환 되도록 자동으로 보장 하므로 .then반환 된 값에 대한 호출을 연결할 수 있습니다 . 이것은 우리가 큰 성공을 거두도록 설정합니다. nPromise 의 순서를 실행 한 다음 성공 / 오류 메시지 표시와 같은 중요한 작업 수행합니다.


trincot의 훌륭한 답변을 기반으로 배열의 각 항목을 실행하는 처리기를 허용하는 재사용 가능한 함수를 작성했습니다. 함수 자체는 루프가 끝날 때까지 기다릴 수있는 promise를 반환하고 전달한 핸들러 함수도 promise를 반환 할 수 있습니다.

loop (items, handler) : 약속

제대로하는 데 시간이 좀 걸렸지 만 다음 코드는 많은 promise-looping 상황에서 사용할 수 있다고 믿습니다.

복사-붙여 넣기 준비 코드 :

// SEE https://stackoverflow.com/a/46295049/286685
const loop = (arr, fn, busy, err, i=0) => {
  const body = (ok,er) => {
    try {const r = fn(arr[i], i, arr); r && r.then ? r.then(ok).catch(er) : ok(r)}
    catch(e) {er(e)}
  }
  const next = (ok,er) => () => loop(arr, fn, ok, er, ++i)
  const run  = (ok,er) => i < arr.length ? new Promise(body).then(next(ok,er)).catch(er) : ok()
  return busy ? run(busy,err) : new Promise(run)
}

용법

이를 사용하려면 첫 번째 인수로 반복 할 배열과 두 번째 인수로 처리기 함수를 호출합니다. 세 번째, 네 번째 및 다섯 번째 인수에 대한 매개 변수를 전달하지 마십시오. 내부적으로 사용됩니다.

const loop = (arr, fn, busy, err, i=0) => {
  const body = (ok,er) => {
    try {const r = fn(arr[i], i, arr); r && r.then ? r.then(ok).catch(er) : ok(r)}
    catch(e) {er(e)}
  }
  const next = (ok,er) => () => loop(arr, fn, ok, er, ++i)
  const run  = (ok,er) => i < arr.length ? new Promise(body).then(next(ok,er)).catch(er) : ok()
  return busy ? run(busy,err) : new Promise(run)
}

const items = ['one', 'two', 'three']

loop(items, item => {
  console.info(item)
})
.then(() => console.info('Done!'))

고급 사용 사례

핸들러 함수, 중첩 루프 및 오류 처리를 살펴 보겠습니다.

핸들러 (현재, 색인, 모두)

핸들러는 3 개의 인수를 전달받습니다. 현재 항목, 현재 항목의 인덱스 및 반복되는 전체 배열입니다. 핸들러 함수가 비동기 작업을 수행해야하는 경우 프라 미스를 반환 할 수 있으며 루프 함수는 다음 반복을 시작하기 전에 프라 미스가 해결 될 때까지 기다립니다. 루프 호출을 중첩 할 수 있으며 모든 것이 예상대로 작동합니다.

const loop = (arr, fn, busy, err, i=0) => {
  const body = (ok,er) => {
    try {const r = fn(arr[i], i, arr); r && r.then ? r.then(ok).catch(er) : ok(r)}
    catch(e) {er(e)}
  }
  const next = (ok,er) => () => loop(arr, fn, ok, er, ++i)
  const run  = (ok,er) => i < arr.length ? new Promise(body).then(next(ok,er)).catch(er) : ok()
  return busy ? run(busy,err) : new Promise(run)
}

const tests = [
  [],
  ['one', 'two'],
  ['A', 'B', 'C']
]

loop(tests, (test, idx, all) => new Promise((testNext, testFailed) => {
  console.info('Performing test ' + idx)
  return loop(test, (testCase) => {
    console.info(testCase)
  })
  .then(testNext)
  .catch(testFailed)
}))
.then(() => console.info('All tests done'))

오류 처리

내가 살펴본 많은 promise-looping 예제는 예외가 발생하면 분해됩니다. 이 기능이 올바른 작업을 수행하도록하는 것은 꽤 까다로 웠지만 제가 알 수있는 한 현재 작동하고 있습니다. 내부 루프에 catch 핸들러를 추가하고 발생하면 거부 함수를 호출하십시오. 예 :

const loop = (arr, fn, busy, err, i=0) => {
  const body = (ok,er) => {
    try {const r = fn(arr[i], i, arr); r && r.then ? r.then(ok).catch(er) : ok(r)}
    catch(e) {er(e)}
  }
  const next = (ok,er) => () => loop(arr, fn, ok, er, ++i)
  const run  = (ok,er) => i < arr.length ? new Promise(body).then(next(ok,er)).catch(er) : ok()
  return busy ? run(busy,err) : new Promise(run)
}

const tests = [
  [],
  ['one', 'two'],
  ['A', 'B', 'C']
]

loop(tests, (test, idx, all) => new Promise((testNext, testFailed) => {
  console.info('Performing test ' + idx)
  loop(test, (testCase) => {
    if (idx == 2) throw new Error()
    console.info(testCase)
  })
  .then(testNext)
  .catch(testFailed)  //  <--- DON'T FORGET!!
}))
.then(() => console.error('Oops, test should have failed'))
.catch(e => console.info('Succesfully caught error: ', e))
.then(() => console.info('All tests done'))

업데이트 : NPM 패키지

이 답변을 작성한 이후로 위의 코드를 NPM 패키지로 변경했습니다.

for-async

설치

npm install --save for-async

수입

var forAsync = require('for-async');  // Common JS, or
import forAsync from 'for-async';

사용량 (비동기)

var arr = ['some', 'cool', 'array'];
forAsync(arr, function(item, idx){
  return new Promise(function(resolve){
    setTimeout(function(){
      console.info(item, idx);
      // Logs 3 lines: `some 0`, `cool 1`, `array 2`
      resolve(); // <-- signals that this iteration is complete
    }, 25); // delay 25 ms to make async
  })
})

자세한 내용은 패키지 readme를 참조하십시오.


ES6로 제한되는 경우 가장 좋은 옵션은 모두 약속입니다. Promise.all(array)또한 array인수의 모든 약속을 성공적으로 실행 한 후 약속 배열을 반환합니다 . 데이터베이스에서 많은 학생 레코드를 업데이트하려는 경우 다음 코드가 이러한 경우에 Promise.all의 개념을 보여줍니다.

let promises = [];
students.map((student, index) => {
  student.rollNo = index + 1;
  student.city = 'City Name';
  //Update whatever information on student you want
  promises.push(student.save());
  //where save() is a function used to save data in mongoDB
});
Promise.all(promises).then(() => {
  //All the save queries will be executed when .then is executed
  //You can do further operations here after as all update operations are completed now
});

Map은 루프에 대한 예제 방법입니다. for또는 forin또는 forEach루프를 사용할 수도 있습니다 . 따라서 개념은 매우 간단합니다. 대량 비동기 작업을 수행하려는 루프를 시작하십시오. 이러한 모든 비동기 작업 문을 해당 루프의 범위 외부에서 선언 된 배열로 푸시합니다. 루프가 완료된 후 준비된 쿼리 / 약속 배열을 인수로 사용하여 Promise all 문을 실행합니다.

기본 개념은 자바 스크립트 루프가 동기식 인 반면 데이터베이스 호출은 비동기식이며 동기화되는 루프에서 푸시 메서드를 사용한다는 것입니다. 따라서 비동기 동작의 문제는 루프 내에서 발생하지 않습니다.


여기 내 2 센트 가치가 있습니다.

  • resuable 기능 forpromise()
  • 고전적인 for 루프를 에뮬레이트합니다.
  • 내부 논리를 기반으로 조기 종료를 허용하고 값을 반환합니다.
  • resolve / next / collect에 전달 된 결과 배열을 수집 할 수 있습니다.
  • defaults to start=0,increment=1
  • exceptions thrown inside loop are caught and passed to .catch()

    function forpromise(lo, hi, st, res, fn) {
        if (typeof res === 'function') {
            fn = res;
            res = undefined;
        }
        if (typeof hi === 'function') {
            fn = hi;
            hi = lo;
            lo = 0;
            st = 1;
        }
        if (typeof st === 'function') {
            fn = st;
            st = 1;
        }
        return new Promise(function(resolve, reject) {

            (function loop(i) {
                if (i >= hi) return resolve(res);
                const promise = new Promise(function(nxt, brk) {
                    try {
                        fn(i, nxt, brk);
                    } catch (ouch) {
                        return reject(ouch);
                    }
                });
                promise.
                catch (function(brkres) {
                    hi = lo - st;
                    resolve(brkres)
                }).then(function(el) {
                    if (res) res.push(el);
                    loop(i + st)
                });
            })(lo);

        });
    }


    //no result returned, just loop from 0 thru 9
    forpromise(0, 10, function(i, next) {
        console.log("iterating:", i);
        next();
    }).then(function() {


        console.log("test result 1", arguments);

        //shortform:no result returned, just loop from 0 thru 4
        forpromise(5, function(i, next) {
            console.log("counting:", i);
            next();
        }).then(function() {

            console.log("test result 2", arguments);



            //collect result array, even numbers only
            forpromise(0, 10, 2, [], function(i, collect) {
                console.log("adding item:", i);
                collect("result-" + i);
            }).then(function() {

                console.log("test result 3", arguments);

                //collect results, even numbers, break loop early with different result
                forpromise(0, 10, 2, [], function(i, collect, break_) {
                    console.log("adding item:", i);
                    if (i === 8) return break_("ending early");
                    collect("result-" + i);
                }).then(function() {

                    console.log("test result 4", arguments);

                    // collect results, but break loop on exception thrown, which we catch
                    forpromise(0, 10, 2, [], function(i, collect, break_) {
                        console.log("adding item:", i);
                        if (i === 4) throw new Error("failure inside loop");
                        collect("result-" + i);
                    }).then(function() {

                        console.log("test result 5", arguments);

                    }).
                    catch (function(err) {

                        console.log("caught in test 5:[Error ", err.message, "]");

                    });

                });

            });


        });



    });

참고URL : https://stackoverflow.com/questions/40328932/javascript-es6-promise-for-loop

반응형