IT

네이티브 XHR을 어떻게 약속합니까?

lottoking 2020. 5. 29. 08:12
반응형

네이티브 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
    });

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

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


I think we can make the top answer much more flexible and reusable by not having it create the XMLHttpRequest object. The only benefit of doing so is that we don't have to write 2 or 3 lines of code ourselves to do it, and it has the enormous drawback of taking away our access to many of the API's features, like setting headers. It also hides properties of the original object from the code that's supposed to handle the response (for both successes and errors). So we can make a more flexible, more widely applicable function by just accepting the XMLHttpRequest object as input and passing it as the result.

This function converts an arbitrary XMLHttpRequest object into a promise, treating non-200 status codes as an error by default:

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

This function fits very naturally into a chain of Promises, without sacrificing the flexibility of the XMLHttpRequest API:

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 was omitted above to keep the sample code simpler. You should always have one, and of course we can:

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

And disabling the HTTP status code handling doesn't require much change in the code:

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

Our calling code is longer, but conceptually, it's still simple to understand what's going on. And we don't have to rebuild the entire web request API just to support its features.

We can add a few convenience functions to tidy up our code, as well:

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

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

Then our code becomes:

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

jpmc26's answer is quite close to perfect in my opinion. It has some drawbacks, though:

  1. It exposes the xhr request only until the last moment. This does not allow POST-requests to set the request body.
  2. It is harder to read as the crucial send-call is hidden inside a function.
  3. It introduces quite a bit of boilerplate when actually making the request.

Monkey patching the xhr-object tackles these issues:

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

Now the usage is as simple as:

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.'));

Of course, this introduces a different drawback: Monkey-patching does hurt performance. However this should not be a problem assuming that the user is waiting mainly for the result of the xhr, that the request itself takes orders of magnitude longer than setting up the call and xhr requests not being sent frequently.

PS: And of course if targeting modern browsers, use fetch!

PPS: It has been pointed out in the comments that this method changes the standard API which can be confusing. For better clarity one could patch a different method onto the xhr object sendAndGetPromise().

참고URL : https://stackoverflow.com/questions/30008114/how-do-i-promisify-native-xhr

반응형