我想使用(本机)承诺在我的前端应用程序执行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);
});

当前回答

如果你想让你的代码在旧的浏览器中工作,把这个放在你的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]-->

其他回答

如果你想让你的代码在旧的浏览器中工作,把这个放在你的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]-->

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

我假设你知道如何做出一个原生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 (xhr.status >= 200 && xhr.status < 300) {
        resolve(xhr.response);
      } else {
        reject({
          status: xhr.status,
          statusText: xhr.statusText
        });
      }
    };
    xhr.onerror = function () {
      reject({
        status: xhr.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 (xhr.status >= 200 && xhr.status < 300) {
        resolve(xhr.response);
      } else {
        reject({
          status: xhr.status,
          statusText: xhr.statusText
        });
      }
    };
    xhr.onerror = function () {
      reject({
        status: xhr.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中可以找到更全面的方法。

或者,您可以使用fetch API (polyfill)。

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

请记住,这段代码只会在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
    });

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

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

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