Promise 的三种状态
- Pending
- Fullfilled
- Rejected
Promise 的状态一旦从 Pending 变为 Fullfilled 或 Rejected,就不会再发生改变。
基本用法
创建 Promise
var promise = new Promise(function(resolve, reject) {
//...
if ( /* 异步操作成功 */ ) {
resolve(value); //将结果传出
} else {
reject(error); //将错误传出
}
});
then 方法
then 方法可以接受两个回调函数作为参数。第一个回调函数是 Promise 对象的状态变为 Resolved 时调用,第二个回调函数是 Promise 对象的状态变为 Rejected 时调用,第二个函数是可选的。这两个函数都接受 Promise 对象传出的值作为参数。Promise 新建后会立即执行,then 方法指定的回调函数将在当前脚本所有同步任务执行完成后才会执行。
function timeout(ms) {
return new Promise((resolve, reject) => {
setTimeout(resolve, ms, 'done');
});
}
timeout(100).then((value) => {
console.log(value);
});
使用 Promise 实现 AJAX
var getJSON = function(url) {
var promise = new Promise(function(resolve, reject) {
var client = new XMLHttpRequest();
client.open("GET", url);
client.onreadystatechange = handler;
client.responseType = "json";
client.setRequestHeader("Accept", "application/json");
client.send();
function handler() {
if (this.readyState !== 4) {
return;
}
if (this.status === 200) {
resolve(this.response);
} else {
reject(new Error(this.statusText));
}
};
});
return promise;
};
getJSON("/post.json").then(function(json) {
console.log('Contents: ' + json);
}, function(error) {
console.log('ERROR', error);
});
then 方法可以链式调用,前一个回调函数完成之后,会将返回结果作为参数传入下一个回调函数。
getJSON("/post/1.json").then(
post => getJSON(post.commentURL)
).then(
comments => console.log("Resolved: ", comments),
err => console.log("Rejected: ", err)
);
catch 方法
catch 方法是 then(null, rejection) 的别名,用于指定发生错误时的回调函数。建议不要在 then 方法中使用 Rejected 参数,推荐总是使用 catch 方法。如果没有 catch 方法指定错误处理的函数,Promise 对象抛出的错误不会传递到外层代码,即不会有任何反应。
all 方法
all 方法用于将多个 Promise 实例包装成一个新的 Promise 实例。
var p = Promise.all([p1, p2, p3]);
上面的代码中,Promise.all 方法接受一个数组作为参数,p1、p2、p3 都是 Promise 对象的实例;如果不是,会先调用 Promise.resolve 方法,将参数转为 Promise 实例,再继续处理(Promise.all 方法的参数不一定是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例)。
p 的状态由 p1、p2、p3 决定,分为两种情况:
- 只有 p1、p2、p3 的状态都变成 Fullfilled,p 的状态才会变成 Fullfilled,此时 p1、p2、p3 的返回值组成一个数组,传递给 p 的回调函数。
- 只要 p1、p2、p3 中有一个被 Rejected,p 的状态就变成 Rejected,此时第一个被 Rejected 的实例的返回值会传递给 p 的回调函数。
如果作为参数的 Promise 实例自身定义了 catch 方法,那么他被 rejected 时并不会触发 Promise.all() 的 catch 方法。
race 方法
race 方法同样是将多个 Promise 实例包装成一个新的 Promise 实例。但 p1、p2、p3 中有一个实例率先改变状态,p 的状态就会改变。那个最先改变的 Promise 实例的返回值就传递给 p 的回调函数。
//如果 5 秒钟内 fetch 没有返回结果,p 将变为 Rejected,触发 catch 方法
const p = Promise.race([
fetch('/request'),
new Promise(function(resolve, reject) {
setTimeout(() => reject(new Error('request timeout')), 5000)
})
]);
p.then(response => console.log(response));
p.catch(error => console.log(error));
resolve 方法
resolve 方法可以将现有对象转为 Promise 对象。分为四种情况:
- 参数是一个 Promise 实例
会直接返回这个实例。
- 参数是一个 thenable 对象
指具有 then 方法的对象:
let thenable = {
then: function(resolve, reject) {
resolve(42);
}
};
resolve 方法会将这个对象转为 Promise 对象,然后立即执行 thenable 对象的 then 方法。
- 参数不是 thenable 对象或不是对象
resolve 方法返回一个新的 Promise 对象,状态为 Resolved。resolve 方法的参数会传给回调函数并立即执行。
- 不带有任何参数
直接返回一个 Resolve 状态的 Promise 对象。
> 立即 resolve 的 Promise 对象是在本轮“事件循环“(event loop)结束时,而不是再下一轮”事件循环“开始时。
setTimeout(function() {
console.log('three');
}, 0);
Promise.resolve().then(function() {
console.log('two');
});
console.log('one');
// one
// two
// three
其中:
console.log('one')
立即执行;Promise.resolve()
在本轮”事件循环“结束时执行;setTimeout(fn, 0)
在下一轮”事件循环“开始时执行。
reject 方法
reject 方法也会返回一个新的 Promise 实例,状态为 Rejected,reject 方法的参数会原封不动地作为 reject 的理由变成后续方法的参数,回调函数会立即执行。
const thenable = {
then(resolve, reject) {
reject('ERROR');
}
};
Promise.reject(thenable)
.catch(e => {
console.log(e === thenable)
})
// true
// Promise.reject 方法的参数是一个 thenable 对象,执行以后,后面 catch 方法的参数不是 reject 抛出的”ERROR“字符串,而是 thenable 对象。
finally 方法
finally 方法用于指定 Promise 对象最后状态如何都会执行的操作,它接受一个普通的回调函数作为参数,该函数不管怎样都必须执行。
同步与异步
让同步函数同步执行,异步函数异步执行,有两种方法。
第一种写法,使用 async
函数:
const f = () => console.log('now');
(async () => f())();
console.log('next');
// now
// next
上面的代码中,第二行是一个立即执行的匿名函数,会立即执行里面的 async
函数,因此如果 f
是同步的,就会得到同步的结果;如果 f
是异步的,就可以用 then
指定下一步,需要注意, async() => f()
会吃掉 f()
抛出的错误,捕获错误需要使用 promise.catch
方法。写法如下:
(async () => f())()
.then(...)
.catch(...)
第二种写法,使用 new Promise()
。
const f = () => console.log('now');
(
() => new Promise(
resolve => resolve(f())
)
)();
console.log('next');
// now
// next
上面的代码也是用立即执行的匿名函数来执行 new Promise()
的。这种情况下,同步函数也是同步执行的。