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)
});
// 出错了
参考链接
作者:阮一峰