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

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);
});



답변

난 당신이 네이티브 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 )를 사용할 수 있습니다 .


답변

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

이 코드는 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
    });


답변

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

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

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


답변

나는 우리가 객체를 만들지 않아도 훨씬 유연하고 재사용 가능한 최상위 답변을 만들 수 있다고 생각 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);
});


답변

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()있습니다.


답변