[javascript] 일부는 거절하더라도 모든 약속이 완료 될 때까지 기다리십시오

Promise네트워크 요청을 하는 일련의 s 가 있다고 가정 해 봅시다 .

// http://does-not-exist will throw a TypeError
var arr = [ fetch('index.html'), fetch('http://does-not-exist') ]

Promise.all(arr)
  .then(res => console.log('success', res))
  .catch(err => console.log('error', err)) // This is executed   

실패했는지 여부에 관계없이 모든 작업이 완료 될 때까지 기다립니다. 내가 없이는 살 수있는 자원에 대한 네트워크 오류가있을 수 있지만 가능한 경우 진행하기 전에 원합니다. 네트워크 장애를 정상적으로 처리하고 싶습니다.

때문에 Promises.all이에 대한 어떤 방을 떠나지 않아하는 약속 라이브러리를 사용하지 않고,이를 처리하기위한 권장 패턴은 무엇인가?



답변

업데이트, 아마도 내장 네이티브를 사용하고 싶을 것입니다 Promise.allSettled.

Promise.allSettled([promise]).then(([result]) => {
   //reach here regardless
   // {status: "fulfilled", value: 33}
});

재미있는 사실로, 아래의 답변은 해당 방법을 언어에 추가하는 데있어 선행 기술이었습니다.]


물론, 당신은 단지 필요합니다 reflect:

const reflect = p => p.then(v => ({v, status: "fulfilled" }),
                            e => ({e, status: "rejected" }));

reflect(promise).then((v => {
    console.log(v.status);
});

또는 ES5와 함께 :

function reflect(promise){
    return promise.then(function(v){ return {v:v, status: "fulfilled" }},
                        function(e){ return {e:e, status: "rejected" }});
}


reflect(promise).then(function(v){
    console.log(v.status);
});

또는 귀하의 예에서 :

var arr = [ fetch('index.html'), fetch('http://does-not-exist') ]

Promise.all(arr.map(reflect)).then(function(results){
    var success = results.filter(x => x.status === "fulfilled");
});


답변

비슷한 대답이지만 ES6에 더 관용적 일 수 있습니다.

const a = Promise.resolve(1);
const b = Promise.reject(new Error(2));
const c = Promise.resolve(3);

Promise.all([a, b, c].map(p => p.catch(e => e)))
  .then(results => console.log(results)) // 1,Error: 2,3
  .catch(e => console.log(e));


const console = { log: msg => div.innerHTML += msg + "<br>"};
<div id="div"></div>

값의 종류에 따라 (들) 오류가 자주 충분히 쉽게 구별 할 수 반환 (예를 들어, 사용 undefined, “걱정하지 않는다”에 대한 typeof일반 비 객체의 값을, result.message, result.toString().startsWith("Error:")등)


답변

Benjamin의 답변은이 문제를 해결하기위한 훌륭한 추상화를 제공하지만 덜 추상화 된 솔루션을 원했습니다. 이 문제를 해결하는 명시적인 방법은 단순히 .catch내부 약속을 호출 하고 콜백에서 오류를 반환하는 것입니다.

let a = new Promise((res, rej) => res('Resolved!')),
    b = new Promise((res, rej) => rej('Rejected!')),
    c = a.catch(e => { console.log('"a" failed.'); return e; }),
    d = b.catch(e => { console.log('"b" failed.'); return e; });

Promise.all([c, d])
  .then(result => console.log('Then', result)) // Then ["Resolved!", "Rejected!"]
  .catch(err => console.log('Catch', err));

Promise.all([a.catch(e => e), b.catch(e => e)])
  .then(result => console.log('Then', result)) // Then ["Resolved!", "Rejected!"]
  .catch(err => console.log('Catch', err));

한 단계 더 나아가 다음과 같은 일반적인 catch 처리기를 작성할 수 있습니다.

const catchHandler = error => ({ payload: error, resolved: false });

그럼 넌 할 수있어

> Promise.all([a, b].map(promise => promise.catch(catchHandler))
    .then(results => console.log(results))
    .catch(() => console.log('Promise.all failed'))
< [ 'Resolved!',  { payload: Promise, resolved: false } ]

이 문제는 catch 된 값이 catch되지 않은 값과 다른 인터페이스를 가지므로이를 정리하기 위해 다음과 같은 작업을 수행 할 수 있습니다.

const successHandler = result => ({ payload: result, resolved: true });

이제 당신은 이것을 할 수 있습니다 :

> Promise.all([a, b].map(result => result.then(successHandler).catch(catchHandler))
    .then(results => console.log(results.filter(result => result.resolved))
    .catch(() => console.log('Promise.all failed'))
< [ 'Resolved!' ]

그런 다음 DRY를 유지하려면 Benjamin의 답변을 얻습니다.

const reflect = promise => promise
  .then(successHandler)
  .catch(catchHander)

지금은 어디에서 보이는지

> Promise.all([a, b].map(result => result.then(successHandler).catch(catchHandler))
    .then(results => console.log(results.filter(result => result.resolved))
    .catch(() => console.log('Promise.all failed'))
< [ 'Resolved!' ]

두 번째 솔루션의 장점은 추상화되어 DRY라는 것입니다. 단점은 더 많은 코드가 있으며 일을 일관성있게 만들기 위해 모든 약속을 반영해야한다는 것입니다.

내 솔루션을 명시 적 및 KISS로 특성화하지만 실제로는 덜 견고합니다. 인터페이스는 약속의 성공 여부를 정확하게 알고 있다고 보증하지 않습니다.

예를 들어 다음이있을 수 있습니다.

const a = Promise.resolve(new Error('Not beaking, just bad'));
const b = Promise.reject(new Error('This actually didnt work'));

이것은에 의해 잡힐하지 않습니다 a.catch때문에,

> Promise.all([a, b].map(promise => promise.catch(e => e))
    .then(results => console.log(results))
< [ Error, Error ]

어느 쪽이 치명적이고 그렇지 않은지를 알 수있는 방법은 없습니다. 그것이 중요하다면, 당신은 그것이 성공했는지 아닌지를 추적하는 인터페이스를 시행하고 싶을 것 reflect입니다.

오류를 정상적으로 처리하려면 오류를 정의되지 않은 값으로 처리하면됩니다.

> Promise.all([a.catch(() => undefined), b.catch(() => undefined)])
    .then((results) => console.log('Known values: ', results.filter(x => typeof x !== 'undefined')))
< [ 'Resolved!' ]

제 경우에는 오류나 오류가 무엇인지 알 필요가 없습니다. 가치가 있는지 여부 만 신경 쓰면됩니다. 약속을 생성하는 함수가 특정 오류를 기록하는 것에 대해 걱정하도록하겠습니다.

const apiMethod = () => fetch()
  .catch(error => {
    console.log(error.message);
    throw error;
  });

이렇게하면 나머지 응용 프로그램은 원하는 경우 오류를 무시하고 원하는 경우 정의되지 않은 값으로 처리 할 수 ​​있습니다.

나는 높은 수준의 기능이 안전하게 실패하고 그 종속성이 실패한 이유에 대한 세부 사항에 대해 걱정하지 않기를 원하며, 그 상충 관계를 만들어야 할 때 KISS를 DRY보다 선호합니다 reflect.


답변

거기에있다 완성 된 제안 바닐라 자바 스크립트, 기본적으로이 작업을 수행 할 수있는 기능 : Promise.allSettled, 4 단계로했다, ES2020에 officialized되고, 그리고 구현되는 모든 현대 환경 . 이 다른 답변reflect기능 과 매우 유사합니다 . 다음은 제안서 페이지의 예입니다. 전에는 다음을 수행해야했습니다.

function reflect(promise) {
  return promise.then(
    (v) => {
      return { status: 'fulfilled', value: v };
    },
    (error) => {
      return { status: 'rejected', reason: error };
    }
  );
}

const promises = [ fetch('index.html'), fetch('https://does-not-exist/') ];
const results = await Promise.all(promises.map(reflect));
const successfulPromises = results.filter(p => p.status === 'fulfilled');

사용 Promise.allSettled하는 대신, 위의가에 해당 될 것입니다 :

const promises = [ fetch('index.html'), fetch('https://does-not-exist/') ];
const results = await Promise.allSettled(promises);
const successfulPromises = results.filter(p => p.status === 'fulfilled');

현대 환경을 사용하는 사람들은 라이브러리없이이 방법을 사용할 수 있습니다 . 이러한 경우 다음 스 니펫은 문제없이 실행되어야합니다.

Promise.allSettled([
  Promise.resolve('a'),
  Promise.reject('b')
])
  .then(console.log);

산출:

[
  {
    "status": "fulfilled",
    "value": "a"
  },
  {
    "status": "rejected",
    "reason": "b"
  }
]

구형 브라우저의 경우 사양 호환 폴리 필이 여기에 있습니다 .


답변

저는 벤자민의 대답을 정말 좋아합니다. 그리고 어떻게 그가 기본적으로 모든 약속을 항상 해결하지만 때로는 오류가 발생했을 때의 약속으로 바꾸는가. 🙂
대안을 찾고있는 경우를 대비하여 귀하의 요청에 대한 나의 시도는 다음과 같습니다. 이 방법은 단순히 오류를 유효한 결과로 취급하며, 다음과 유사하게 코딩 Promise.all됩니다.

Promise.settle = function(promises) {
  var results = [];
  var done = promises.length;

  return new Promise(function(resolve) {
    function tryResolve(i, v) {
      results[i] = v;
      done = done - 1;
      if (done == 0)
        resolve(results);
    }

    for (var i=0; i<promises.length; i++)
      promises[i].then(tryResolve.bind(null, i), tryResolve.bind(null, i));
    if (done == 0)
      resolve(results);
  });
}


답변

var err;
Promise.all([
    promiseOne().catch(function(error) { err = error;}),
    promiseTwo().catch(function(error) { err = error;})
]).then(function() {
    if (err) {
        throw err;
    }
});

Promise.all거부 된 약속을 삼켜 약속을 모두 해결 한 때 반환됩니다, 그래서 변수에 오류를 저장합니다. 그런 다음 오류를 다시 발생 시키거나 무엇이든 할 수 있습니다. 이런 식으로, 첫 번째 거부 대신 마지막 거부를 얻을 것입니다.


답변

나는 같은 문제가 있었고 다음과 같은 방법으로 해결했습니다.

const fetch = (url) => {
  return node-fetch(url)
    .then(result => result.json())
    .catch((e) => {
      return new Promise((resolve) => setTimeout(() => resolve(fetch(url)), timeout));
    });
};

tasks = [fetch(url1), fetch(url2) ....];

Promise.all(tasks).then(......)

이 경우 Promise.all모든 약속이 들어올 때까지 기다리 resolved거나rejected 상태.

이 솔루션 catch을 사용하면 비 차단 방식으로 “실행 중지 “가됩니다. 실제로, 우리는 아무것도 멈추지 않고 타임 아웃 후에 해결되면 Promise다른 것을 반환하는 보류 상태로 돌아갑니다 Promise.