Promise对象
1. Promise概述
Promise
是异步编程的一种解决方案,它比传统的额解决方案--回调函数和事件更合理和强大。
所谓Promise
可以简单理解为一个容器,里面保存未来才能有结果的事件,比如客户端发出的请求,但是它会在事件成功或者失败后改变状态,并且也只会因为里面保存的事件的结果而改变状态,状态改变后就凝固了,即不能再改变为其他状态了。
Promise
对象有三种状态,分别是pending
,fulfilled
(已成功)和rejected(已失败)
,但确切来说,一个Promise
对象只会有两种状态,即pending
(进行中)和resolved
(已凝固)因为它的状态一旦改变就会凝固,再也无法改变。
2. 基本用法
ES6 提供Promise
对象的构造函数,使我们可以很方便的使用Promise
对象。
const p = new Promise((resolve, reject) => {
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
})
Promise
对象一旦生成后就会立即执行,然后根据执行结果改变状态,我们可以使用Promise
的实例方法then
来获取事件结果。
p.then(res => {
console.log(res)
}, err => {
console.log(err)
})
then
方法接受两个回调函数作为参数,第一个回调是fulfilled
时调用的函数,第二个回调则是rejected
时调用的函数。
下面这个例子会帮我们进一步了解Promise
对象的特性。
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('hello')
}, 0)
reject('bye')
})
promise.then(res => {
console.log(`${res} world`)
}, err => {
console.log(`${err} world`)
})
// bye world
上面的代码中,我们在Promise
对象中写了分别写了resolve
和reject
,但是因为resolve
使用了setTimeout
方法,会让resolve
方法在主线程执行完毕后才会执行,所以是reject
先执行,这时,Promise
对象就会变成rejected
,并返回结果。
下面是一个用Promise
实现ajax
操作的例子:
const getJSON = function(url) {
const promise = new Promise(function(resolve, reject){
const handler = function() {
if (this.readyState !== 4) {
return;
}
if (this.status === 200) {
resolve(this.response);
} else {
reject(new Error(this.statusText));
}
};
const client = new XMLHttpRequest();
client.open("GET", url);
client.onreadystatechange = handler;
client.responseType = "json";
client.setRequestHeader("Accept", "application/json");
client.send();
});
return promise;
};
getJSON("/posts.json").then(function(json) {
console.log('Contents: ' + json);
}, function(error) {
console.error('出错了', error);
});
上面代码中,getJSON
是对 XMLHttpRequest
对象的封装,用于发出一个针对JSON
数据的 HTTP
请求,并且返回一个Promise
对象。需要注意的是,在getJSON
内部,resolve
函数和reject
函数调用时,都带有参数。
另外,还有一个比较重要的点,就是resolve
以及reject
的作用并不等同于return
,尽管调用了上述的两个方法,接下来的语句仍然会执行。
const p = new Promise((resolve, reject) => {
resolve('world')
console.log('hello')
return 'promise'
})
p.then(res => {
console.log(res)
})
// hello
// world
通过上面的代码我们可以看到即使我们调用resolve
方法,但是下面的语句仍然执行了,并且我们也可以得知,在Promise
中使用return
不能达到我们预期的效果,因为then
只接受resolve
和reject
的参数。
3. Promise.prototype.then()
then
方法是定义在Promise
的原型上的,所以每一个Promise
实例都可以调用这个方法,then
方法接受两个函数作为参数。
const promise = new Promise((re, rj) => {
rj(new Error('fail in promise'))
})
promise.then(null, err => {
console.log(err)
throw(new Error('fail in another promise'))
}).then(null, err => {
console.log(err)
})
// Error: fail in promise
// Error: fail in another promise
通过上面的代码我们可以看出,then
方法返回的仍然是一个Promise
实例,所以我们仍然可以后面调用then
方法。
4. Promise.prototype.catch()
Promise.prototype.catch
方法是.then(null, rejection
)或.then(undefined, rejection)
的别名,用于指定发生错误时的回调函数。
// 写法一
const promise = new Promise(function(resolve, reject) {
try {
throw new Error('test');
} catch(e) {
reject(e);
}
});
promise.catch(function(error) {
console.log(error);
});
// 写法二
const promise = new Promise(function(resolve, reject) {
reject(new Error('test'));
});
promise.catch(function(error) {
console.log(error);
});
通过上面的代码我们可以看出catch
方法既可以作为then
方法的第二个参数,来处理Promise
的rejected
状态,同时也可以捕获Promise
内部的错误,所以阮一峰大神在书中写道,我们更推荐使用catch
方法来替代then
方法的第二个参数,因为这样可以使得Promise
变成一个try...catch
结构,可以防止Promise
内部错误冒泡出去。
const someAsyncThing = function() {
return new Promise(function(resolve, reject) {
// 下面一行会报错,因为x没有声明
resolve(x + 2);
});
};
someAsyncThing().then(function() {
console.log('everything is great');
});
setTimeout(() => { console.log(123) }, 2000);
// Uncaught (in promise) ReferenceError: x is not defined
// 123
一般总是建议,Promise
对象后面要跟catch
方法,这样可以处理Promise
内部发生的错误。catch
方法返回的还是一个 Promise
对象,因此后面还可以接着调用then
方法。
5. Promise.prototype.finally()
书中讲到,finally
其实是一个then
的特里,因为如果我们调用then
方法,会根据Promise
的状态来判断到底执行哪一个回调,但是finally
是无论怎样都会执行,所以说,finally
并不依赖于Promise
的状态,我们可以简单理解为:
promise
.finally(() => {
// 语句
});
// 等同于
promise
.then(
result => {
// 语句
return result;
},
error => {
// 语句
throw error;
}
);
它的实现也很简单:
Promise.prototype.finally = function (callback) {
let P = this.constructor;
return this.then(
value => P.resolve(callback()).then(() => value),
reason => P.resolve(callback()).then(() => { throw reason })
);
};
6. 其他的实例方法
这里的方法我只记录了说明,如果读者朋友有兴趣,请参阅原著。
6.1 Promise.all()
const p = Promise.all([p1, p2, p3]);
all()
,该方法用于将多个Promise
实例包装成一个新的Promise
实例,all()
方法接受一个数组或者具有Iterator
接口的数据结构作为参数,该数组内的元素为Promise
对象,包装后的Promise
对象的状态由成员决定:
- 如果所有的成员都为
fulfilled
,那么该对象的状态变成fulfilled
,并且将成员的返回值按顺序组成一个数组传给回调参数。 - 如果成员中有一个为
rejected
,那么该对象就会变成rejected
,并且将第一个被rejected
的成员的返回值传递给回调参数。
6.2 Promise.race()
const p = Promise.race([p1, p2, p3]);
race()
方法同样是将多个Promise
实例包装为一个新的Promise
实例,如果有成员不是Promise
,那么就会用Promise.resolve()
方法将其转为Promise
实例,该方法的作用是取成员中最快改变状态的的那一个成员作为该对象的状态,并且将返回值作为参数传递给回调函数。
下面是一个例子,如果指定时间内没有获得结果,就将Promise
的状态变为reject
,否则变为resolve
。
const p = Promise.race([
fetch('/resource-that-may-take-a-while'),
new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error('request timeout')), 5000)
})
]);
p
.then(console.log())
.catch(console.error);
6.3 Promise.allSettled()
Promise.allSettled()
方法接受一组Promise
实例作为参数,包装成一个新的 Promise
实例。只有等到所有这些参数实例都返回结果,不管是fulfilled
还是rejected
,包装实例才会结束。该方法由 ES2020 引入。
该方法返回的新的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()
的返回值allSettledPromise
,状态只可能变成fulfilled
。它的监听函数接收到的参数是数组results
。该数组的每个成员都是一个对象,对应传入Promise.allSettled()
的两个Promise
实例。每个对象都有status
属性,该属性的值只可能是字符串fulfilled
或字符串rejected
。fulfilled
时,对象有value
属性,rejected
时有reason
属性,对应两种状态的返回值。
6.4 Promise.any()
Promise.any()
方法接受一组Promise
实例作为参数,包装成一个新的 Promise
实例。只要参数实例有一个变成fulfilled
状态,包装实例就会变成fulfilled
状态;如果所有参数实例都变成rejected
状态,包装实例就会变成rejected
状态。
6.5 Promise.resolve()
resolve
的作用就是将现有对象转为Promise
对象,该方法如果接受的参数不是一个对象的会,就会返回一个新的Promise
对象,状态为resolved
6.6 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)
});
// 出错了
参考链接
作者:阮一峰