development

일련의 약속을 동기화하는 방법은 무엇입니까?

big-blog 2021. 1. 5. 21:05
반응형

일련의 약속을 동기화하는 방법은 무엇입니까?


나는 배열에 나열된 것과 동일한 순서로 해결되어야하는 promise 객체의 배열이 있습니다. 즉, 이전 항목이 해결 될 때까지 요소를 해결하려고 시도 할 수 없습니다 (메서드 Promise.all([...])처럼).

그리고 하나의 요소가 거부되면 다음 요소를 해결하려고 시도하지 않고 즉시 거부 할 체인이 필요합니다.

이것을 어떻게 구현할 수 sequence있습니까? 아니면 그러한 패턴에 대한 기존 구현이 있습니까?

function sequence(arr) {
    return new Promise(function (resolve, reject) {
        // try resolving all elements in 'arr',
        // but strictly one after another;
    });
}

편집하다

초기 답변 sequence은 그러한 배열 요소의 결과 만 가능하며 , 그러한 예에서 미리 정의되어 있기 때문에 실행이 아니라고 제안 합니다.

그렇다면 조기 실행을 피하는 방식으로 일련의 약속을 생성하는 방법은 무엇입니까?

다음은 수정 된 예입니다.

function sequence(nextPromise) {
    // while nextPromise() creates and returns another promise,
    // continue resolving it;
}

나는 그것이 같은 문제의 일부라고 믿기 때문에 그것을 별도의 질문으로 만들고 싶지 않습니다.

해결책

아래의 일부 답변과 그에 따른 토론은 약간 잘못되었지만 내가 찾고 있던 것을 정확히 수행 한 최종 솔루션 은 메서드 시퀀스spex 라이브러리 내에서 구현 되었습니다 . 이 메서드는 일련의 동적 길이를 반복 할 수 있으며 애플리케이션의 비즈니스 논리에 필요한 약속을 생성 할 수 있습니다.

나중에 모든 사람이 사용할 수있는 공유 라이브러리로 전환했습니다.


다음은 각 비동기 작업을 연속적으로 실행하는 배열을 통해 시퀀스하는 방법에 대한 몇 가지 간단한 예입니다.

항목 배열이 있다고 가정 해 보겠습니다.

var arr = [...];

그리고 배열의 각 항목에 대해 특정 비동기 작업을 한 번에 하나씩 순차적으로 수행하여 이전 작업이 완료 될 때까지 다음 작업이 시작되지 않도록합니다.

그리고 배열의 항목 중 하나를 처리하기 위해 함수를 반환하는 약속이 있다고 가정 해 보겠습니다 fn(item).

수동 반복

function processItem(item) {
    // do async operation and process the result
    // return a promise
}

그런 다음 다음과 같이 할 수 있습니다.

function processArray(array, fn) {
    var index = 0;

    function next() {
        if (index < array.length) {
            fn(array[index++]).then(next);
        }
    }
    next();
}

processArray(arr, processItem);

약속을 반환하는 수동 반복

약속 processArray()이 언제 완료되었는지 알 수 있도록 반환 된 약속을 원하면 다음을 추가 할 수 있습니다.

function processArray(array, fn) {
    var index = 0;

    function next() {
        if (index < array.length) {
            return fn(array[index++]).then(function(value) {
                // apply some logic to value
                // you have three options here:
                // 1) Call next() to continue processing the result of the array
                // 2) throw err to stop processing and result in a rejected promise being returned
                // 3) return value to stop processing and result in a resolved promise being returned
                return next();
            });
        }
    } else {
        // return whatever you want to return when all processing is done
        // this returne value will be the ersolved value of the returned promise.
        return "all done";
    }
}

processArray(arr, processItem).then(function(result) {
    // all done here
    console.log(result);
}, function(err) {
    // rejection happened
    console.log(err);
});

참고 : 이렇게하면 첫 번째 거부시 체인이 중지되고 해당 이유가 processArray가 promise를 반환 한 것으로 다시 전달됩니다.

.reduce ()를 사용한 반복

약속으로 더 많은 작업을 수행하려면 모든 약속을 연결할 수 있습니다.

function processArray(array, fn) {
   return array.reduce(function(p, item) {
       return p.then(function() {
          return fn(item);
       });
   }, Promise.resolve());
}

processArray(arr, processItem).then(function(result) {
    // all done here
}, function(reason) {
    // rejection happened
});

참고 : 이렇게하면 첫 번째 거부시 체인이 중지되고 해당 이유가에서 반환 된 promise로 다시 전달됩니다 processArray().

성공 시나리오의 경우에서 반환 된 promise는 콜백 processArray()의 마지막으로 해결 된 값으로 해결 fn됩니다. 결과 목록을 누적하고 fn그로 해결 하려면 결과를 클로저 배열에서 수집하고 매번 해당 배열을 계속 반환하여 최종 해결이 결과 배열이되도록 할 수 있습니다.

배열로 해결되는 .reduce ()를 사용한 반복

그리고 이제 최종 약속 결과가 데이터 배열 (순서대로)이되기를 원한다는 것이 분명해 보이기 때문에 다음을 생성하는 이전 솔루션의 개정판이 있습니다.

function processArray(array, fn) {
   var results = [];
   return array.reduce(function(p, item) {
       return p.then(function() {
           return fn(item).then(function(data) {
               results.push(data);
               return results;
           });
       });
   }, Promise.resolve());
}

processArray(arr, processItem).then(function(result) {
    // all done here
    // array of data here in result
}, function(reason) {
    // rejection happened
});

작업 데모 : http://jsfiddle.net/jfriend00/h3zaw8u8/

거부를 보여주는 작업 데모 : http://jsfiddle.net/jfriend00/p0ffbpoc/

지연이있는 배열로 해결되는 .reduce ()를 사용한 반복

그리고 작업 사이에 약간의 지연을 삽입하려는 경우 :

function delay(t, v) {
    return new Promise(function(resolve) {
        setTimeout(resolve.bind(null, v), t);
    });
}

function processArrayWithDelay(array, t, fn) {
   var results = [];
   return array.reduce(function(p, item) {
       return p.then(function() {
           return fn(item).then(function(data) {
               results.push(data);
               return delay(t, results);
           });
       });
   }, Promise.resolve());
}

processArray(arr, 200, processItem).then(function(result) {
    // all done here
    // array of data here in result
}, function(reason) {
    // rejection happened
});

Bluebird Promise 라이브러리를 사용한 반복

Bluebird promise 라이브러리에는 많은 동시성 제어 기능이 내장되어 있습니다. 예를 들어, 배열을 통한 시퀀스 반복을 위해 Promise.mapSeries().

Promise.mapSeries(arr, function(item) {
    // process each individual item here, return a promise
    return processItem(item);
}).then(function(results) {
    // process final results here
}).catch(function(err) {
    // process array here
});

또는 반복 사이에 지연을 삽입하려면 :

Promise.mapSeries(arr, function(item) {
    // process each individual item here, return a promise
    return processItem(item).delay(100);
}).then(function(results) {
    // process final results here
}).catch(function(err) {
    // process array here
});

ES7 async / await 사용

async / await를 지원하는 환경에서 코딩하는 경우 일반 for루프를 사용한 다음 루프 await에서 promise를 사용할 수도 있습니다. 그러면 for진행하기 전에 promise가 해결 될 때까지 루프가 일시 중지됩니다. 이렇게하면 비동기 작업의 순서를 효과적으로 지정할 수 있으므로 이전 작업이 완료 될 때까지 다음 작업이 시작되지 않습니다.

async function processArray(array, fn) {
    let results = [];
    for (let i = 0; i < array.length; i++) {
        let r = await fn(array[i]);
        results.push(r);
    }
    return results;    // will be resolved value of promise
}

// sample usage
processArray(arr, processItem).then(function(result) {
    // all done here
    // array of data here in result
}, function(reason) {
    // rejection happened
});

참고로 processArray()여기 함수 Promise.map()는 배열과 약속 생성 함수를 사용하고 해결 된 결과 배열로 해결되는 약속을 반환하는 Bluebird promise 라이브러리 와 매우 유사 하다고 생각 합니다.


@ vitaly-t-여기에 귀하의 접근 방식에 대한 몇 가지 자세한 설명이 있습니다. 당신에게 가장 적합한 코드는 무엇이든 환영합니다. 내가 처음 약속을 사용하기 시작했을 때, 나는 그들이 한 가장 단순한 일에만 약속을 사용하는 경향이 있었고, 약속의 고급 사용이 나를 위해 훨씬 더 많은 일을 할 수있을 때 많은 논리를 직접 작성하는 경향이있었습니다. 당신은 당신이 완전히 편하고 그 이상으로 편한 것만 사용하고, 오히려 당신이 친밀하게 알고있는 당신 자신의 코드를보고 싶어합니다. 그것은 아마도 인간의 본성 일 것입니다.

Promise가 저를 위해 무엇을 할 수 있는지 점점 더 많이 이해하게되면서 이제는 Promise의 고급 기능을 더 많이 사용하는 코드를 작성하는 것을 좋아하고 제게는 완벽하게 자연스러워 보이며 잘 구축하고있는 것 같습니다. 유용한 기능이 많이있는 테스트 된 인프라. 나는 당신이 잠재적으로 그 방향으로 나아 가기 위해 점점 더 많은 것을 배우면서 당신의 마음을 열어두기를 바랍니다. 이해도가 향상됨에 따라 마이그레이션하는 것이 유용하고 생산적인 방향이라고 생각합니다.

귀하의 접근 방식에 대한 몇 가지 구체적인 피드백 사항은 다음과 같습니다.

7 곳에서 약속을 만듭니다.

스타일과는 대조적으로, 내 코드에는 명시 적으로 새 promise를 생성하는 곳만 있습니다. 한 번은 팩토리 함수에서, 한 번은 .reduce()루프 를 초기화합니다 . 다른 모든 곳에서는 이미 생성 된 약속에 연결하거나 그 안에 값을 반환하거나 직접 반환함으로써 생성 된 약속을 기반으로합니다. 코드에는 약속을 생성하는 7 개의 고유 한 위치가 있습니다. 이제 좋은 코딩은 약속을 생성 할 수있는 장소가 얼마나 적은지 확인하는 경쟁이 아니지만, 이는 이미 생성 된 약속을 활용하고 조건을 테스트하고 새로운 약속을 생성하는 데있어 차이를 지적 할 수 있습니다.

투척 안전은 매우 유용한 기능입니다.

약속은 던져 안전합니다. 즉, promise 처리기 내에서 throw 된 예외는 자동으로 해당 promise를 거부합니다. 예외가 거부 되기만하려는 경우이 기능은 활용하기에 매우 유용한 기능입니다. 사실, 자신을 던지는 것은 또 다른 약속을 만들지 않고 핸들러 내에서 거부하는 유용한 방법이라는 것을 알게 될 것입니다.

많은 Promise.resolve()또는 Promise.reject()아마 단순화 수있는 기회입니다

Promise.resolve()또는 Promise.reject()이 많이 포함 된 코드를 보면 이러한 모든 새로운 약속을 만드는 것보다 기존 약속을 더 잘 활용할 수있는 기회가있을 것입니다.

약속에 캐스팅

무언가가 약속을 반환했는지 모르는 경우 약속에 캐스팅 할 수 있습니다. 그러면 프라 미스 라이브러리는 그것이 프라 미스인지 아닌지, 사용중인 프라 미스 라이브러리와 일치하는 종류의 프라 미스인지 여부를 자체적으로 확인하고 그렇지 않은 경우 하나로 래핑합니다. 이렇게하면이 논리의 많은 부분을 직접 다시 작성하지 않아도됩니다.

약속 반환 계약

요즘 대부분의 경우, 약속을 반환하기 위해 비동기 작업을 수행 할 수있는 함수에 대한 계약을 체결하는 것이 완전히 실행 가능합니다. 함수가 동기식으로 작업을 수행하려는 경우 해결 된 약속을 반환 할 수 있습니다. 당신은 이것이 부담 스럽다고 느끼는 것 같지만, 확실히 바람이 불고있는 방식이고 나는 이미 그것을 요구하는 많은 코드를 작성하고 있으며 약속에 익숙해지면 매우 자연스럽게 느껴집니다. 작업이 동기화인지 비동기인지를 추상화하고 호출자는 어떤 방식 으로든 특별한 것을 알거나 수행 할 필요가 없습니다. 이것은 약속의 좋은 사용입니다.

팩토리 함수는 하나의 promise 만 생성하도록 작성할 수 있습니다.

팩토리 함수는 하나의 약속 만 생성 한 다음이를 해결하거나 거부하도록 작성 될 수 있습니다. 이 스타일은 또한 안전하게 던지므로 팩토리 함수에서 발생하는 모든 예외는 자동으로 거부됩니다. 또한 항상 약속을 자동으로 반환하도록 계약을 만듭니다.

이 팩토리 함수가 자리 표시 자 함수라는 것을 알고 있지만 (비동기 작업도 수행하지 않음) 고려할 스타일을 볼 수 있습니다.

function factory(idx) {
    // create the promise this way gives you automatic throw-safety
    return new Promise(function(resolve, reject) {
        switch (idx) {
            case 0:
                resolve("one");
                break;
            case 1:
                resolve("two");
                break;
            case 2:
                resolve("three");
                break;
            default:
                resolve(null);
                break;
        }
    });
}

이러한 작업 중 하나가 비동기 적이면 다음과 같이 하나의 중앙 약속에 자동으로 연결되는 자체 약속을 반환 할 수 있습니다.

function factory(idx) {
    // create the promise this way gives you automatic throw-safety
    return new Promise(function(resolve, reject) {
        switch (idx) {
            case 0:
                resolve($.ajax(...));
            case 1:
                resole($.ajax(...));
            case 2:
                resolve("two");
                break;
            default:
                resolve(null);
                break;
        }
    });
}

거부 처리기를 사용하는 return promise.reject(reason)것은 필요하지 않습니다.

이 코드 본문이있는 경우 :

    return obj.then(function (data) {
        result.push(data);
        return loop(++idx, result);
    }, function (reason) {
        return promise.reject(reason);
    });

거부 처리기가 값을 추가하지 않습니다. 대신 다음과 같이 할 수 있습니다.

    return obj.then(function (data) {
        result.push(data);
        return loop(++idx, result);
    });

이미 결과를 반환하고 있습니다 obj.then(). 하나 만약 obj거부 또는 아무것도에 체인 경우 obj또는 다음에서 반환 된 .then()핸들러 거부하고 obj거부합니다. 따라서 거부와 함께 새로운 약속을 만들 필요가 없습니다. 거부 처리기가없는 더 간단한 코드는 더 적은 코드로 동일한 작업을 수행합니다.


다음은 이러한 아이디어의 대부분을 통합하려는 코드의 일반 아키텍처 버전입니다.

function factory(idx) {
    // create the promise this way gives you automatic throw-safety
    return new Promise(function(resolve, reject) {
        switch (idx) {
            case 0:
                resolve("zero");
                break;
            case 1:
                resolve("one");
                break;
            case 2:
                resolve("two");
                break;
            default:
                // stop further processing
                resolve(null);
                break;
        }
    });
}


// Sequentially resolves dynamic promises returned by a factory;
function sequence(factory) {
    function loop(idx, result) {
        return Promise.resolve(factory(idx)).then(function(val) {
            // if resolved value is not null, then store result and keep going
            if (val !== null) {
                result.push(val);
                // return promise from next call to loop() which will automatically chain
                return loop(++idx, result);
            } else {
                // if we got null, then we're done so return results
                return result;
            }
        });
    }
    return loop(0, []);
}

sequence(factory).then(function(results) {
    log("results: ", results);
}, function(reason) {
    log("rejected: ", reason);
});

작업 데모 : http://jsfiddle.net/jfriend00/h3zaw8u8/

이 구현에 대한 몇 가지 의견 :

  1. Promise.resolve(factory(idx))본질적으로 결과를 factory(idx)약속에 던집니다 . 그저 값이라면 그 반환 값을 해결 값으로 사용하여 해결 된 약속이됩니다. 이미 약속이라면 그 약속에 연결됩니다. 따라서 factory()함수 의 반환 값에 대한 모든 유형 검사 코드를 대체합니다 .

  2. 팩토리 함수는 null해결 된 값 을 반환 하는 약속을 반환하여 완료되었음을 알립니다 null. 위의 캐스트는이 두 조건을 동일한 결과 코드에 매핑합니다.

  3. 팩토리 함수는 예외를 자동으로 포착하여 거부로 전환 한 다음 sequence()함수에서 자동으로 처리 합니다. 이는 처리를 중단하고 첫 번째 예외 또는 거부시 오류를 피드백하려는 경우 약속이 많은 오류 처리를 수행하도록하는 중요한 이점 중 하나입니다.

  4. 이 구현의 팩토리 함수는 프로 미스 또는 정적 값 (동기 작업의 경우)을 반환 할 수 있으며 설계 요청에 따라 잘 작동합니다.

  5. 나는 팩토리 함수의 promise 콜백에서 throw 된 예외를 사용하여 테스트했으며 실제로 예외를 이유로 시퀀스 약속을 거부하기 위해 해당 예외를 거부하고 전파합니다.

  6. 이것은 여러 호출을 연결하기 위해 (의도적으로 일반 아키텍처를 유지하려고) 유사한 방법을 사용합니다 loop().


약속은 작업 자체가 아니라 작업의 가치나타냅니다 . 작업이 이미 시작 되었으므로 서로를 기다리게 할 수 없습니다.

대신 약속 을 순서대로 호출하는 함수를 동기화 하거나 (예 : promise 체인이있는 루프를 통해) .each블루 버드 메서드를 사용하여 동기화 할 수 있습니다 .


단순히 X 비동기 작업을 실행 한 다음 순서대로 해결되기를 원할 수는 없습니다.

이와 같은 작업을 수행하는 올바른 방법은 이전 작업이 해결 된 후에 만 ​​새 비동기 작업을 실행하는 것입니다.

doSomethingAsync().then(function(){
   doSomethingAsync2().then(function(){
       doSomethingAsync3();
       .......
   });
});

편집
모든 약속을 기다린 다음 특정 순서로 콜백을 호출하려는 것처럼 보입니다. 이 같은:

var callbackArr = [];
var promiseArr = [];
promiseArr.push(doSomethingAsync());
callbackArr.push(doSomethingAsyncCallback);
promiseArr.push(doSomethingAsync1());
callbackArr.push(doSomethingAsync1Callback);
.........
promiseArr.push(doSomethingAsyncN());
callbackArr.push(doSomethingAsyncNCallback);

그리고:

$.when(promiseArr).done(function(promise){
    while(callbackArr.length > 0)
    {
       callbackArr.pop()(promise);
    }
});

이로 인해 발생할 수있는 문제는 하나 이상의 약속이 실패 할 때입니다.


매우 조밀하지만 다음은 값 배열에 대해 약속 반환 함수를 반복하고 결과 배열로 해결하는 또 다른 솔루션입니다.

function processArray(arr, fn) {
    return arr.reduce(
        (p, v) => p.then((a) => fn(v).then(r => a.concat([r]))),
        Promise.resolve([])
    );
}

용법:

const numbers = [0, 4, 20, 100];
const multiplyBy3 = (x) => new Promise(res => res(x * 3));

// Prints [ 0, 12, 60, 300 ]
processArray(numbers, multiplyBy3).then(console.log);

한 약속에서 다음 약속으로 축소하고 있기 때문에 각 항목은 연속적으로 처리됩니다.

@ jfriend00의 "배열로 해결되는 .reduce ()를 사용한 반복"솔루션과 기능적으로 동일하지만 조금 더 깔끔합니다.


In my opinion, you should be using a for loop(yes the only time I would recommend a for loop). The reason is that when you are using a for loop it allows you to await on each of the iterations of your loop where using reduce, map or forEach with run all your promise iterations concurrently. Which by the sounds of it is not what you want, you want each promise to wait until the previous promise has resolved. So to do this you would do something like the following.

const ids = [0, 1, 2]
const accounts = ids.map(id => getId(id))
const accountData = async() => {
   for await (const account of accounts) {
       // account will equal the current iteration of the loop
       // and each promise are now waiting on the previous promise to resolve! 
   }
}

// then invoke your function where ever needed
accountData()

And obviously, if you wanted to get really extreme you could do something like this:

 const accountData = async(accounts) => {
    for await (const account of accounts) {
       // do something
    }
 }

 accountData([0, 1, 2].map(id => getId(id)))

This is so much more readable than any of the other examples, it is much less code, reduced the number of lines needed for this functionality, follows a more functional programming way of doing things and is using ES7 to its full potential!!!!

Also depending on your set up or when you are reading this you may need to add the plugin-proposal-async-generator-functions polyfill or you may see the following error

@babel/plugin-proposal-async-generator-functions (https://git.io/vb4yp) to the 'plugins' section of your Babel config to enable transformation.


I suppose two approaches for handling this question:

  1. Create multiple promises and use the allWithAsync function as follow:
let allPromiseAsync = (...PromisesList) => {
return new Promise(async resolve => {
    let output = []
    for (let promise of PromisesList) {
        output.push(await promise.then(async resolvedData => await resolvedData))
        if (output.length === PromisesList.length) resolve(output)
    }
}) }
const prm1= Promise.resolve('first');
const prm2= new Promise((resolve, reject) => setTimeout(resolve, 2000, 'second'));
const prm3= Promise.resolve('third');

allPromiseAsync(prm1, prm2, prm3)
    .then(resolvedData => {
        console.log(resolvedData) // ['first', 'second', 'third']
    });
  1. Use the Promise.all function instead:
  (async () => {
  const promise1 = new Promise(resolve => {
    setTimeout(() => { console.log('first');console.log(new Date());resolve() }, 1000)
  })

  const promise2 = new Promise(resolve => {
    setTimeout(() => {console.log('second');console.log(new Date());  resolve() }, 3000)
  })

  const promise3 = new Promise(resolve => {
    setTimeout(() => { console.log('third');console.log(new Date()); resolve() }, 7000)
  })

  const promises = [promise1, promise2, promise3]

  await Promise.all(promises)

  console.log('This line is shown after 7000ms')
})()

ReferenceURL : https://stackoverflow.com/questions/29880715/how-to-synchronize-a-sequence-of-promises

반응형