我如何保证本地XHR?

2020/10/19 11:22 · javascript ·  · 0评论

我想在我的前端应用程序中使用(本机)promise来执行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请求(您可以在这里这里进行刷牙

由于任何支持本机Promise的浏览器也将支持xhr.onload,因此我们可以跳过所有的onReadyStateChangetomfoolery。让我们退后一步,从使用回调的基本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数据),但足以使我们前进。

Promise构造函数

我们可以这样构造一个承诺:

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

promise构造函数采用一个函数,该函数将传递两个参数(我们称它们为resolvereject)。您可以将它们视为回调,一个代表成功,另一个代表失败。示例很棒,让我们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);
});

现在,我们可以利用promise的功能,将多个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参数和自定义标头。让我们使用带有签名的options对象而不是多个参数:

{
  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可以找到更全面的方法

另外,您可以使用提取APIpolyfill)。

这可能和下面的代码一样简单。

请记住,此代码仅rejectonerror被调用时才触发回调仅适用于网络错误),而不会在HTTP状态代码表示错误触发这还将排除所有其他例外。国际海事组织,应由您自行处理。

另外,建议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对象转换为Promise,默认情况下将非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();
    });
}

此函数非常自然地适合Promises,而不会牺牲XMLHttpRequestAPI的灵活性

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

我们的调用代码更长,但是从概念上讲,了解正在发生的事情仍然很简单。而且,我们不必为了支持其功能而重建整个Web请求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的结果,请求本身花费的时间量比建立呼叫长且xhr请求不经常发送,则这应该不是问题。

PS:当然,如果要针对现代浏览器,请使用fetch!

PPS:在评论中已经指出,此方法更改了可能令人困惑的标准API。为了更好地说明,可以将另一种方法修补到xhr对象上sendAndGetPromise()

如果您希望代码在旧的浏览器中运行,请将其放在HTML文档的<head>中:

<script>
self.Promise||document.write("<script src=/path/to/promise/polyfill.js><\/script>");
</script>

将/path/to/promise/polyfill.js替换为Promise polyfill的路径。如果该类不是本机类,则将创建一个Promise类,并允许您的代码在旧的浏览器(例如Internet Explorer)上运行。Internet Explorer和其他旧版浏览器只占很小的市场份额,这看似微不足道,但这仍然可以转化为数百万的用户,因此,我不建议您完全撤消这些用户。

我可以建议这个Promise polyfill:

https://github.com/taylorhakes/promise-polyfill

现在,您可以访问Promise类。

如果要让代码在IE 6-8等真正的旧浏览器中运行,则需要使用onreadystatechange而不是onload。这没有什么害处,因为onreadystatechange仍在所有当前浏览器中使用,以实现向后兼容:

xhr = new XMLHttpRequest();
xhr.open("GET", "/some/file", true);
promise = new Promise(function (resolve, reject) {
    xhr.onreadystatechange = function() {
        if(this.readyState==4) {
            if (this.status>=200 && this.status<400) {
                resolve(this);
            }
            else {
                reject(this);
            }
        }
    }
    xhr.onerror = function() {
        reject(this);
    }

});
xhr.send()
xhr = null// stop memory leak
promise.then(function(xhr) {
    //success
}).catch(function(xhr) {
    //fail
});

请记住,IE 6不支持XMLHttpRequest,因此您也需要使用ActiveX进行填充。文档<head>中的以下内容可能会起作用:

<!--[if lt IE 7]>
<script>
// This is just an example. Use at your own risk.
function XMLHttpRequest() {
    try {
        return new ActiveXObject("Msxml2.XMLHTTP.6.0")
    }
    catch (e) {
        return new ActiveXObject("Msxml2.XMLHTTP.3.0")
    }
}
</script>
<![endif]-->
本文地址:http://javascript.askforanswer.com/woruhebaozhengbendixhr.html
文章标签: ,   ,  
版权声明:本文为原创文章,版权归 javascript 所有,欢迎分享本文,转载请保留出处!

文件下载

老薛主机终身7折优惠码boke112

上一篇:
下一篇:

评论已关闭!