기본 XHR을 어떻게 약속합니까?


183

XHR 요청을 수행하기 위해 프론트 엔드 앱에서 (기본) 약속을 사용하고 싶지만 거대한 프레임 워크의 모든 부담을 안고 싶습니다.

내 XHR이 약속을 반환하려면하지만이 작동하지 않습니다 (저를주는 : Uncaught TypeError: Promise resolver undefined is not a function)

function makeXHRRequest (method, url, done) {
  var xhr = new XMLHttpRequest();
  xhr.open(method, url);
  xhr.onload = function() { return new Promise().resolve(); };
  xhr.onerror = function() { return new Promise().reject(); };
  xhr.send();
}

makeXHRRequest('GET', 'http://example.com')
.then(function (datums) {
  console.log(datums);
});

답변:


369

난 당신이 네이티브 XHR 요청을 만드는 방법을 알고 있으리라 믿고있어 (당신은 브러시 수 있습니다 여기여기 )

때문에 지원하는 네이티브 약속이 있음을 모든 브라우저 도 지원합니다 xhr.onload, 우리는 모든 건너 뛸 수 있습니다 onReadyStateChange어리 석음을. 뒤로 물러서서 콜백을 사용하여 기본 XHR 요청 기능으로 시작해 봅시다

function makeRequest (method, url, done) {
  var xhr = new XMLHttpRequest();
  xhr.open(method, url);
  xhr.onload = function () {
    done(null, xhr.response);
  };
  xhr.onerror = function () {
    done(xhr.response);
  };
  xhr.send();
}

// And we'd call it as such:

makeRequest('GET', 'http://example.com', function (err, datums) {
  if (err) { throw err; }
  console.log(datums);
});

만세! 여기에는 커스텀 헤더 또는 POST 데이터와 같이 굉장히 복잡한 것이 없지만 앞으로 나아가기에 충분합니다.

약속 생성자

우리는 다음과 같은 약속을 구성 할 수 있습니다.

new Promise(function (resolve, reject) {
  // Do some Async stuff
  // call resolve if it succeeded
  // reject if it failed
});

promise 생성자는 두 개의 인수를 전달하는 함수를 사용합니다 ( resolve및 호출 reject). 이를 콜백, 성공 및 실패에 대한 콜백이라고 생각할 수 있습니다. 예제는 훌륭합니다. makeRequest이 생성자로 업데이트하겠습니다 :

function makeRequest (method, url) {
  return new Promise(function (resolve, reject) {
    var xhr = new XMLHttpRequest();
    xhr.open(method, url);
    xhr.onload = function () {
      if (this.status >= 200 && this.status < 300) {
        resolve(xhr.response);
      } else {
        reject({
          status: this.status,
          statusText: xhr.statusText
        });
      }
    };
    xhr.onerror = function () {
      reject({
        status: this.status,
        statusText: xhr.statusText
      });
    };
    xhr.send();
  });
}

// Example:

makeRequest('GET', 'http://example.com')
.then(function (datums) {
  console.log(datums);
})
.catch(function (err) {
  console.error('Augh, there was an error!', err.statusText);
});

이제 여러 XHR 호출을 연결하여 약속의 힘을 활용할 수 있습니다 (그리고 .catch어느 호출에서든 오류가 발생합니다).

makeRequest('GET', 'http://example.com')
.then(function (datums) {
  return makeRequest('GET', datums.url);
})
.then(function (moreDatums) {
  console.log(moreDatums);
})
.catch(function (err) {
  console.error('Augh, there was an error!', err.statusText);
});

POST / PUT 매개 변수와 사용자 지정 헤더를 모두 추가하여이를 더욱 향상시킬 수 있습니다. 서명과 함께 여러 인수 대신 옵션 객체를 사용합시다.

{
  method: String,
  url: String,
  params: String | Object,
  headers: Object
}

makeRequest 이제 다음과 같이 보입니다.

function makeRequest (opts) {
  return new Promise(function (resolve, reject) {
    var xhr = new XMLHttpRequest();
    xhr.open(opts.method, opts.url);
    xhr.onload = function () {
      if (this.status >= 200 && this.status < 300) {
        resolve(xhr.response);
      } else {
        reject({
          status: this.status,
          statusText: xhr.statusText
        });
      }
    };
    xhr.onerror = function () {
      reject({
        status: this.status,
        statusText: xhr.statusText
      });
    };
    if (opts.headers) {
      Object.keys(opts.headers).forEach(function (key) {
        xhr.setRequestHeader(key, opts.headers[key]);
      });
    }
    var params = opts.params;
    // We'll need to stringify if we've been given an object
    // If we have a string, this is skipped.
    if (params && typeof params === 'object') {
      params = Object.keys(params).map(function (key) {
        return encodeURIComponent(key) + '=' + encodeURIComponent(params[key]);
      }).join('&');
    }
    xhr.send(params);
  });
}

// Headers and params are optional
makeRequest({
  method: 'GET',
  url: 'http://example.com'
})
.then(function (datums) {
  return makeRequest({
    method: 'POST',
    url: datums.url,
    params: {
      score: 9001
    },
    headers: {
      'X-Subliminal-Message': 'Upvote-this-answer'
    }
  });
})
.catch(function (err) {
  console.error('Augh, there was an error!', err.statusText);
});

보다 포괄적 인 접근 방식은 MDN 에서 찾을 수 있습니다 .

또는 페치 API ( polyfill )를 사용할 수 있습니다 .


3
또한 옵션에 추가 할 수 있습니다 responseType, 인증, 자격 증명을 timeout... 그리고 params객체 모양 / bufferviews하고 지원해야 FormData인스턴스
BERGI

4
거부시 새로운 오류를 반환하는 것이 더 좋습니까?
prasanthv

1
또한, 그것은 반환에 이해가되지 않습니다 xhr.status그리고 xhr.statusText그들은 그 경우에 비어 있기 때문에, 에러.
dqd

2
이 코드는 한 가지를 제외하고는 광고 된 것처럼 작동합니다. GET 요청과 함께 매개 변수를 전달하는 올바른 방법은 xhr.send (params)를 통한 것이라고 예상했습니다. 그러나 GET 요청은 send () 메소드로 전송 된 모든 값을 무시합니다. 대신 URL 자체에서 쿼리 문자열 매개 변수이면됩니다. 따라서 위의 방법에서 "params"인수를 GET 요청에 적용하려면 GET vs. POST를 인식하도록 루틴을 수정 한 다음 해당 값을 xhr로 전달되는 URL에 조건부로 추가해야합니다. .열다().
hairbo

1
하나는 사용해야 resolve(xhr.response | xhr.responseText);repsonse 그 동안에서 responseText에 대부분의 브라우저에서.
heinob

50

이것은 다음 코드처럼 간단 할 수 있습니다.

이 코드는 HTTP 상태 코드가 오류를 나타내는 경우가 아니라 호출 된 경우 ( 네트워크 오류 만) reject콜백을 발생시킵니다. 다른 모든 예외도 제외됩니다. 그 취급은 당신에게 달려 있습니다, IMO.onerror

또한 이벤트 자체가 아닌 reject인스턴스로 콜백 을 호출하는 것이 좋지만 Error간단하게하기 위해 그대로 두었습니다.

function request(method, url) {
    return new Promise(function (resolve, reject) {
        var xhr = new XMLHttpRequest();
        xhr.open(method, url);
        xhr.onload = resolve;
        xhr.onerror = reject;
        xhr.send();
    });
}

그리고 이것을 호출하면 다음과 같습니다.

request('GET', 'http://google.com')
    .then(function (e) {
        console.log(e.target.response);
    }, function (e) {
        // handle errors
    });

14
@ MadaraUchiha 나는 그것이 그것의 tl; dr 버전이라고 생각한다. OP에게 질문에 대한 답변을 제공합니다.
Peleg

본문은 POST 요청입니까?
caub

1
@crl은 일반적인 XHR과 마찬가지로 :xhr.send(requestBody)
Peleg

예, 왜 코드에서 허용하지 않습니까? (방법을 매개 변수화
했으므로

6
이 답변은 질문에 대한 답변을 즉시 처리 할 수있는 매우 간단한 코드를 제공하므로 좋아합니다.
Steve Chamaillard 2016 년

12

지금 이것을 찾는 사람이라면 누구나 인출 기능을 사용할 수 있습니다 . 꽤 좋은 지원이 있습니다.

fetch('http://example.com/movies.json')
  .then(response => response.json())
  .then(data => console.log(data));

나는 처음 @SomeKittens의 답변을 사용했지만 그 fetch즉시 상자에서 나를 위해 그것을 발견 했습니다 :)


2
이전 브라우저는이 fetch기능을 지원하지 않지만 GitHub는 polyfill을 게시했습니다 .
bdesham

1
fetch아직 취소를 지원하지 않으므로 권장 하지 않습니다.
James Dunne


stackoverflow.com/questions/31061838/… 의 답변 은 취소 가능 페치 코드 예제를 가지고 있으며 Firefox 57 이상 및 Edge 16 이상에서 작동합니다.
sideshowbarker 1

1
@ microo8 fetch를 사용하여 간단한 예제를 작성하는 것이 좋을 것입니다.
jpaugh

8

나는 우리가 객체를 만들지 않아도 훨씬 유연하고 재사용 가능한 최상위 답변을 만들 수 있다고 생각 XMLHttpRequest합니다. 그렇게하는 것의 유일한 이점은 우리가 직접 2 ~ 3 줄의 코드를 작성할 필요가 없으며 헤더 설정과 같은 많은 API 기능에 대한 액세스 권한을 빼앗는 데 엄청난 단점이 있다는 것입니다. 또한 응답을 처리해야하는 코드 (성공 및 오류 모두)에서 원본 객체의 속성을 숨 깁니다. 따라서 XMLHttpRequest객체를 입력으로 받아 들여 결과 로 전달 함으로써보다 유연하고 광범위하게 적용 가능한 기능을 만들 수 있습니다 .

이 함수는 XMLHttpRequest기본적으로 200이 아닌 상태 코드를 오류로 처리 하여 임의의 객체를 약속으로 변환합니다 .

function promiseResponse(xhr, failNon2xx = true) {
    return new Promise(function (resolve, reject) {
        // Note that when we call reject, we pass an object
        // with the request as a property. This makes it easy for
        // catch blocks to distinguish errors arising here
        // from errors arising elsewhere. Suggestions on a 
        // cleaner way to allow that are welcome.
        xhr.onload = function () {
            if (failNon2xx && (xhr.status < 200 || xhr.status >= 300)) {
                reject({request: xhr});
            } else {
                resolve(xhr);
            }
        };
        xhr.onerror = function () {
            reject({request: xhr});
        };
        xhr.send();
    });
}

이 함수 PromiseXMLHttpRequestAPI 의 유연성을 희생시키지 않으면 서 체인에 매우 자연스럽게 맞습니다 .

Promise.resolve()
.then(function() {
    // We make this a separate function to avoid
    // polluting the calling scope.
    var xhr = new XMLHttpRequest();
    xhr.open('GET', 'https://stackoverflow.com/');
    return xhr;
})
.then(promiseResponse)
.then(function(request) {
    console.log('Success');
    console.log(request.status + ' ' + request.statusText);
});

catch샘플 코드를 더 단순하게 유지하기 위해 위에서 생략했습니다. 당신은 항상 하나를 가져야하며 물론 우리는 할 수 있습니다 :

Promise.resolve()
.then(function() {
    var xhr = new XMLHttpRequest();
    xhr.open('GET', 'https://stackoverflow.com/doesnotexist');
    return xhr;
})
.then(promiseResponse)
.catch(function(err) {
    console.log('Error');
    if (err.hasOwnProperty('request')) {
        console.error(err.request.status + ' ' + err.request.statusText);
    }
    else {
        console.error(err);
    }
});

HTTP 상태 코드 처리를 비활성화하면 코드를 크게 변경할 필요가 없습니다.

Promise.resolve()
.then(function() {
    var xhr = new XMLHttpRequest();
    xhr.open('GET', 'https://stackoverflow.com/doesnotexist');
    return xhr;
})
.then(function(xhr) { return promiseResponse(xhr, false); })
.then(function(request) {
    console.log('Done');
    console.log(request.status + ' ' + request.statusText);
});

우리의 호출 코드는 더 길지만 개념적으로는 무슨 일이 일어나고 있는지 이해하는 것은 간단합니다. 또한 기능을 지원하기 위해 전체 웹 요청 API를 다시 작성할 필요가 없습니다.

코드를 정리하기 위해 몇 가지 편리한 기능을 추가 할 수 있습니다.

function makeSimpleGet(url) {
    var xhr = new XMLHttpRequest();
    xhr.open('GET', url);
    return xhr;
}

function promiseResponseAnyCode(xhr) {
    return promiseResponse(xhr, false);
}

그런 다음 코드는 다음과 같습니다.

Promise.resolve(makeSimpleGet('https://stackoverflow.com/doesnotexist'))
.then(promiseResponseAnyCode)
.then(function(request) {
    console.log('Done');
    console.log(request.status + ' ' + request.statusText);
});

5

jpmc26의 대답은 제 생각에는 완벽에 가깝습니다. 그러나 몇 가지 단점이 있습니다.

  1. 마지막 순간까지만 xhr 요청을 노출합니다. 이것은 POST-requests가 요청 본문을 설정 하도록 허용하지 않습니다 .
  2. 중요한 send호출이 함수 안에 숨겨져 있으므로 읽기가 더 어렵습니다 .
  3. 실제로 요청할 때 꽤 많은 상용구를 소개합니다.

xhr 객체를 패치하는 원숭이는 다음과 같은 문제를 해결합니다.

function promisify(xhr, failNon2xx=true) {
    const oldSend = xhr.send;
    xhr.send = function() {
        const xhrArguments = arguments;
        return new Promise(function (resolve, reject) {
            // Note that when we call reject, we pass an object
            // with the request as a property. This makes it easy for
            // catch blocks to distinguish errors arising here
            // from errors arising elsewhere. Suggestions on a 
            // cleaner way to allow that are welcome.
            xhr.onload = function () {
                if (failNon2xx && (xhr.status < 200 || xhr.status >= 300)) {
                    reject({request: xhr});
                } else {
                    resolve(xhr);
                }
            };
            xhr.onerror = function () {
                reject({request: xhr});
            };
            oldSend.apply(xhr, xhrArguments);
        });
    }
}

이제 사용법은 다음과 같이 간단합니다.

let xhr = new XMLHttpRequest()
promisify(xhr);
xhr.open('POST', 'url')
xhr.setRequestHeader('Some-Header', 'Some-Value')

xhr.send(resource).
    then(() => alert('All done.'),
         () => alert('An error occured.'));

물론 이것은 다른 단점을 가져옵니다. 원숭이 패치는 성능을 저하시킵니다. 그러나 이것은 사용자가 주로 xhr의 결과를 기다리고 있다고 가정하고, 요청 자체가 호출 설정보다 x 더 긴 시간이 걸리고 xhr 요청이 자주 전송되지 않는다고 가정하면 문제가되지 않습니다.

추신 : 물론 최신 브라우저를 대상으로하는 경우 가져 오기를 사용하십시오!

PPS :이 방법은 혼란 스러울 수있는 표준 API를 변경한다는 의견에서 지적되었습니다. 명확성을 높이기 위해 xhr 객체에 다른 메소드를 패치 할 수 sendAndGetPromise()있습니다.


놀랍기 때문에 원숭이 패치를 피합니다. 대부분의 개발자는 표준 API 함수 이름이 표준 API 함수를 호출 할 것으로 기대합니다. 이 코드는 여전히 실제 send호출을 숨기지 만 send반환 값이 없다는 것을 알고있는 독자에게는 혼란을 줄 수 있습니다 . 보다 명시적인 호출을 사용하면 추가 논리가 호출 된 것이 더 명확 해집니다. 내 답변은 인수를 처리하도록 조정해야합니다 send. 그러나 fetch지금 사용하는 것이 좋습니다 .
jpmc26

나는 그것이 달려 있다고 생각합니다. xhr 요청을 반환 / 노출하면 (어쨌든 모호한 것처럼 보입니다) 절대적으로 옳습니다. 그러나 왜 모듈 내 에서이 작업을 수행하지 않으며 결과 약속 만 노출하는지 알 수 없습니다.
t.animal

나는 여러분이 코드를 관리해야하는 사람을 특별히 언급하고 있습니다.
jpmc26

내가 말했듯이 : 그것은 달려 있습니다. 모듈이 너무 커서 약속의 기능이 나머지 코드 사이에서 손실되면 다른 문제가있을 수 있습니다. 일부 엔드 포인트를 호출하고 약속을 반환하려는 모듈이 있다면 문제가 없습니다.
t.animal

코드베이스의 크기에 따라 다릅니다. 표준 API 함수가 표준 동작 이외의 작업을 수행하는 것은 혼란 스럽습니다.
jpmc26
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.