描述
Promise
Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。
简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。
一个 Promise
对象代表一个在这个 promise 被创建出来时不一定已知的值。它让您能够把异步操作最终的成功返回值或者失败原因和相应的处理程序关联起来。 这样使得异步方法可以像同步方法那样返回值:异步方法并不会立即返回最终的值,而是会返回一个 promise
,以便在未来某个时候把值交给使用者。
一个 Promise 必然处于以下几种状态之一
:
- 待定(pending): 初始状态,既没有被兑现,也没有被拒绝。
- 已兑现(fulfilled): 意味着操作成功完成。
- 已拒绝(rejected): 意味着操作失败。
一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。
只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved
(已定型)。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
Ajax
AJAX 是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术。
AJAX = Asynchronous JavaScript and XML(异步的 JavaScript 和 XML)。
AJAX 不是新的编程语言,而是一种使用现有标准的新方法。
Ajax请求的五个步骤:
- 创建一个XMLHttpRequest异步对象
- 设置请求方式和请求地址
- 用send发送请求
- 监听状态变化
- 接收返回的数据
创建对象
Promise
const promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
复制代码
Ajax
var client;
if (window.XMLHttpRequest){
// code for IE7+, Firefox, Chrome, Opera, Safari
client=new XMLHttpRequest();
}
else{
// code for IE6, IE5
client=new ActiveXObject("Microsoft.XMLHTTP");
}
client.open("POST", 'url');
client.responseType = "json";
// Accept表示发送端(客户端)希望接收的数据类型
client.setRequestHeader("Accept", "application/json");
// Content-Type代表发送端(客户端|服务器)发送的实体数据的数据类型,如果不写这一条
client.setRequestHeader("Content-Type","application/json;charset=UTF-8");
client.send(JSON.stringify({username:'admin',password:'123456'}));
// 存储函数(或函数名),每当 readyState 属性改变时,就会调用该函数。
client.onreadystatechange = function(){
// readyState存有 XMLHttpRequest 的状态。从 0 到 4 发生变化。
// 0: 请求未初始化
// 1: 服务器连接已建立
// 2: 请求已接收
// 3: 请求处理中
// 4: 请求已完成,且响应已就绪
// status 200: "OK"
if(this.readyState === 4&&this.status === 200){
console.log('成功');
}else{
console.log('失败');
}
};
复制代码
Promise+Ajax进行一个封装
下面是仿照jq的ajax去进行的一个封装
function request({url,method,data,timeout,success,fail}){
let client;
console.log(url,method,data)
return new Promise((resolve,reject) => {
if (window.XMLHttpRequest){
// code for IE7+, Firefox, Chrome, Opera, Safari
client=new XMLHttpRequest();
}
else{
// code for IE6, IE5
client=new ActiveXObject("Microsoft.XMLHTTP");
}
client.open(method, url);
client.responseType = "json";
client.timeout = timeout;
client.setRequestHeader("Content-type", "application/json;charset=UTF-8");
client.setRequestHeader("Accept", "application/json");
client.setRequestHeader("Access-Control-Allow-Origin", "*");
client.send(JSON.stringify(data));
client.onreadystatechange = function(){
if(this.readyState !== 4){
return;
};
if(this.status === 200){
success.call(undefined,this.response)
}else{
fail.call(undefined,this.statusText)
}
};
})
};
request({
url:'http://127.0.0.1:3000/login/loginSubmit',
method:'post',
timeout:120000,
data:{
username:'admin',
password:'123456'
},
success: res => {
console.log(res)
},
fail: err => {
console.log(err)
}
});
复制代码
Promise基本用法
Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数。
// bad
promise
.then(function(data) {
// success
}, function(err) {
// error
});
// good
promise
.then(function(data) { //cb
// success
})
.catch(function(err) {
// error
});
复制代码
then
方法可以接受两个回调函数作为参数。第一个回调函数是Promise
对象的状态变为resolved时调用,第二个回调函数是Promise
对象的状态变为rejected时调用。这两个函数都是可选的,不一定要提供。它们都接受Promise
对象传出的值作为参数。
上面代码中,第二种写法要好于第一种写法,理由是第二种写法可以捕获前面then方法执行中的错误,也更接近同步的写法(try/catch
)。因此,建议总是使用catch()
方法,而不使用then()
方法的第二个参数。
Promise 新建后就会立即执行。
let promise = new Promise(function(resolve, reject) {
console.log('Promise');
resolve();
});
promise.then(function() {
console.log('resolved.');
});
console.log('Hi!');
// Promise
// Hi!
// resolved
复制代码
Promise.prototype.finally()
finally()方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。
promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});
复制代码
Promise.all()
Promise.all()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
const p = Promise.all([p1, p2, p3]);
复制代码
上面代码中,Promise.all()方法接受一个数组作为参数,p1、p2、p3都是 Promise 实例,如果不是,就会先调用下面讲到的Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。另外,Promise.all()方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。
p的状态由p1、p2、p3决定,分成两种情况。
(1)只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
(2)只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
Promise.race()
Promise.race()方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。
const p = Promise.race([p1, p2, p3]);
复制代码
上面代码中,只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。
Promise.allSettled()
Promise.allSettled()方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。只有等到所有这些参数实例都返回结果,不管是fulfilled还是rejected,包装实例才会结束。该方法返回的新的 Promise 实例,一旦结束,状态总是fulfilled,不会变成rejected。状态变成fulfilled后,Promise 的监听函数接收到的参数是一个数组,每个成员对应一个传入Promise.allSettled()
的 Promise 实例。
const resolved = Promise.resolve(42);
const rejected = Promise.reject(-1);
const allSettledPromise = Promise.allSettled([resolved, rejected]);
allSettledPromise.then(function (results) {
console.log(results);
});
// [
// { status: 'fulfilled', value: 42 },
// { status: 'rejected', reason: -1 }
// ]
复制代码
有时候,我们不关心异步操作的结果,只关心这些操作有没有结束。这时,Promise.allSettled()方法就很有用。
Promise.any()
Promise.any()
方法。该方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例返回。只要参数实例有一个变成fulfilled状态,包装实例就会变成fulfilled状态;如果所有参数实例都变成rejected状态,包装实例就会变成rejected状态。
Promise.any()
抛出的错误,不是一个一般的错误,而是一个 AggregateError
实例。它相当于一个数组,每个成员对应一个被rejected的操作所抛出的错误。下面是 AggregateError
的实现示例。
var resolved = Promise.resolve(42);
var rejected = Promise.reject(-1);
var alsoRejected = Promise.reject(Infinity);
Promise.any([resolved, rejected, alsoRejected]).then(function (result) {
console.log(result); // 42
});
Promise.any([rejected, alsoRejected]).catch(function (results) {
console.log(results); // [-1, Infinity]
});
复制代码
Promise.resolve()
有时需要将现有对象转为 Promise 对象,Promise.resolve()
方法就起到这个作用。
Promise.resolve('foo')
// 等价于
new Promise(resolve => resolve('foo'))
复制代码
Promise.resolve()
方法的参数分成四种情况。
1. 参数是一个 Promise
实例
如果参数是 Promise 实例,那么Promise.resolve将不做任何修改、原封不动地返回这个实例。
2. 参数是一个thenable
对象
thenable对象指的是具有then方法的对象,比如下面这个对象。
let thenable = {
then: function(resolve, reject) {
resolve(42);
}
};
复制代码
Promise.resolve()
方法会将这个对象转为 Promise 对象,然后就立即执行thenable
对象的then()
方法。
let thenable = {
then: function(resolve, reject) {
resolve(42);
}
};
let p1 = Promise.resolve(thenable);
p1.then(function (value) {
console.log(value); // 42
});
复制代码
3. 参数不是具有then()
方法的对象,或根本就不是对象
如果参数是一个原始值,或者是一个不具有then()方法的对象,则Promise.resolve()方法返回一个新的 Promise 对象,状态为resolved。
const p = Promise.resolve('Hello');
p.then(function (s) {
console.log(s)
});
// Hello
复制代码
上面代码生成一个新的 Promise 对象的实例p。由于字符串Hello不属于异步操作(判断方法是字符串对象不具有 then 方法),返回 Promise 实例的状态从一生成就是resolved,所以回调函数会立即执行。Promise.resolve()方法的参数,会同时传给回调函数。
4. 不带有任何参数
Promise.resolve()
方法允许调用时不带参数,直接返回一个resolved状态的 Promise 对象。
所以,如果希望得到一个 Promise 对象,比较方便的方法就是直接调用Promise.resolve()方法。
需要注意的是,立即resolve()
的 Promise 对象,是在本轮“事件循环”(event loop)的结束时执行,而不是在下一轮“事件循环”的开始时。
setTimeout(function () {
console.log('three');
}, 0);
Promise.resolve().then(function () {
console.log('two');
});
console.log('one');
// one
// two
// three
复制代码
Promise.reject()
Promise.reject(reason)
方法也会返回一个新的 Promise 实例,该实例的状态为rejected。
const p = Promise.reject('出错了');
// 等同于
const p = new Promise((resolve, reject) => reject('出错了'))
p.then(null, function (s) {
console.log(s)
});
// 出错了
复制代码
Promise.try()
Promise.try()
就是模拟try代码块,就像promise.catch模拟的是catch代码块。
只不过不会影响同步异步,同步依旧按照同步方式进行,异步按照异步进行。
Promise.try(() => database.users.get({id: userId}))
.then(...)
.catch(...)
复制代码
详细了解promise点击阮一峰-es6入门-promise