我想使用(本机)承诺在我的前端应用程序执行XHR请求,但没有一个庞大的框架的所有愚蠢。

我想我的xhr返回一个承诺,但这并不工作(给我:Uncaught TypeError:承诺解析器未定义不是一个函数)

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

当前回答

对于现在搜索这个的人,你可以使用fetch函数。 它有一些很好的支持。

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

我首先使用了@ somecats的答案,但后来发现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.

这个函数将任意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();
    });
}

这个函数非常自然地适合一个promise链,而不牺牲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。你应该一直有一个,当然我们可以:

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

这可以像下面的代码一样简单。

请记住,这段代码只会在onerror被调用(仅限网络错误)时触发拒绝回调,而不会在HTTP状态代码表示错误时触发。这也将排除所有其他异常。在我看来,这些应该由你来处理。

此外,建议使用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
    });

如果你想让你的代码在旧的浏览器中工作,把这个放在你的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等旧浏览器上运行。ie和其他老浏览器只占了一小部分市场份额,这看起来微不足道,但这仍然意味着数百万用户,所以我不建议完全抛弃这些用户。

请允许我推荐这个承诺填充:

https://github.com/stefanpenner/es6-promise/

现在您可以访问Promise类。

如果你想让你的代码在像IE 6-8这样的老浏览器中工作,你需要使用onreadystatechange而不是onload。这样做并没有什么坏处,因为onreadystatechange仍然在当前所有浏览器中使用,以实现向后兼容:

function send_request(xhr, data, timeout) {
    return new Promise(function (resolve, reject) {
        var s, p, i;
        if (data && data.constructor==Object) {// serialize object
            s = "_="+(new Date).getTime();
            for (p in data) if (data.hasOwnProperty(p)) {
                if (!data[p] || data[p].constructor!=Array) {
                    data[p] = [data[p]]
                }
                for (i=0; i<data[p].length; i++) {
                    s+= "&"+encodeuricomponent(p)+"="+encodeuricomponent(data[p][i]);
                }
            }
            data = s;
        }
        xhr.onreadystatechange = function() {
            if (xhr.readyState==4) {
                resolve(xhr);
            }
        }
        xhr.send(data);
        if (timeout) {
            settimeout(function() {
                reject("timeout");
                xhr.abort();
            }, timeout);// milliseconds until timeout
        }
    });
}

xhr = new XMLHttpRequest();
xhr.open("GET", "/some/file", true);
send_request(xhr).then(function(xhr) {
    if (xhr.status>=200 || xhr.status<400) {
        //success
        alert(xhr.responseText);
    }
    else {
        return Promise.reject(xhr.statusText? xhr.status+" "+xhr.statusText: "error");
    }
})

请记住,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]-->

对于现在搜索这个的人,你可以使用fetch函数。 它有一些很好的支持。

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

我首先使用了@ somecats的答案,但后来发现fetch为我做了这件事:)

在我看来,Jpmc26的答案非常接近完美。不过,它也有一些缺点:

它只在最后一刻才公开xhr请求。这不允许POST-requests设置请求体。 由于关键的send-call隐藏在函数中,因此很难阅读。 在实际提出请求时,它引入了相当多的样板文件。

Monkey补丁xhr-object解决了以下问题:

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()上修补一个不同的方法。