1. 简介
ES6引入了一个全新的对象Promise,用于表示一个异步操作的最终状态(完成或失败),以及其返回的值。Promise最直接的好处就是链式调用,另外在错误捕获上也很方便。用同步的写法解决异步问题,代码直观,易于理解维护,解决了回调地狱的问题。关于Promise的详细讲解和更多用例我会开专门文章讨论。这里我们主要看一下Promise及其原型的属性和方法。
2. Promise对象创建
Promise对象使用new构造函数创建。基本使用方法如下:
const promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。
then方法可以接受两个回调函数作为参数。第一个回调函数是Promise对象的状态变为resolved时调用,第二个回调函数是Promise对象的状态变为rejected时调用。其中,第二个函数是可选的,不一定要提供。这两个函数都接受Promise对象传出的值作为参数。
var promise = new Promise(function(resolve, reject) {
setTimeout(resolve, 1000, 'foo');
});
promise.then(function(val){
console.log(val); // 1000ms后输出 ‘foo’
})
3. Promise构造函数的属性与方法
我们用Object.getOwnPropertyNames()方法获取Promise构造函数的所有属性与方法。
Object.getOwnPropertyNames(Promise);
// (7) ["length", "name", "prototype", "all", "race", "resolve", "reject"]
发现一共有7个属性和方法。
3.1 Promise构造函数的属性
Promise.length
长度总为1 (构造器参数的数目)
Promise.name
名称为"Promise"
Promise.prototype
指向Promise构造函数的原型,可以为所有 Promise 类型的对象添加属性。
3.2 Promise构造函数的方法
Promise.all(iterable)
这个方法返回一个新的promise对象,该promise对象在iterable参数对象里所有的promise对象都成功的时候才会触发成功,一旦有任何一个iterable里面的promise对象失败则立即触发该promise对象的失败。这个新的promise对象在触发成功状态以后,会把一个包含iterable里所有promise返回值的数组作为成功回调的返回值,顺序跟iterable的顺序保持一致;如果这个新的promise对象触发了失败状态,它会把iterable里第一个触发失败的promise对象的错误信息作为它的失败错误信息。Promise.all方法常被用于处理多个promise对象的状态集合。(可以参考jQuery.when方法---MDN Promise译者注)
var promise1 = Promise.resolve(3);
var promise2 = 42;
var promise3 = new Promise(function(resolve, reject) {
setTimeout(resolve, 100, 'foo');
});
Promise.all([promise1, promise2, promise3]).then(function(values) {
console.log(values);
});
// expected output: Array [3, 42, "foo"]
当然,当参数不包含 Promise 时, 该方法返回完成(resolve),但这显然没有什么意义。
Promise.race(iterable)
当iterable参数里的任意一个子promise被成功或失败后,父promise马上也会用子promise的成功返回值或失败详情作为参数调用父promise绑定的相应句柄,并返回该promise对象。
var promise1 = new Promise(function(resolve, reject) {
setTimeout(resolve, 500, 'one');
});
var promise2 = new Promise(function(resolve, reject) {
setTimeout(resolve, 100, 'two');
});
Promise.race([promise1, promise2]).then(function(value) {
console.log(value);
// Both resolve, but promise2 is faster
});
// expected output: "two"
Promise.reject(reason)
返回一个状态为失败的Promise对象,并将给定的失败信息reason(Promise被拒绝的原因)传递给对应的处理方法。此处使用Error实例的reason对调试和选择性错误捕捉很有帮助。
Promise.reject("Testing static reject").then(function(reason) {
// 未被调用
}, function(reason) {
console.log(reason); // "Testing static reject"
});
Promise.reject(new Error("fail")).then(function(error) {
// 未被调用
}, function(error) {
console.log(error); // 堆栈跟踪
/* Error: fail
at <anonymous>:7:16 */
});
Promise.resolve(value)
返回一个状态由给定value决定的Promise对象。如果该值是一个Promise对象,则直接返回该对象;如果该值是thenable(即,带有then方法的对象),返回的Promise对象的最终状态由then方法执行决定;否则的话(该value为空,基本类型或者不带then方法的对象),返回的Promise对象状态为fulfilled,并且将该value传递给对应的then方法。通常而言,如果你不知道一个值是否是Promise对象,使用Promise.resolve(value) 来返回一个Promise对象,这样就能将该value以Promise对象形式使用。
用法如下:
Promise.resolve(promise);
直接返回该对象promisePromise.resolve(thenable);
返回一个最终状态由then方法执行决定的Promise对象Promise.resolve(value)
value为空,基本类型,或者不带then方法的对象,返回状态为fulfilled的Promise对象,并且将该value传递给对应的then方法
基本用法示例:
var promise1 = Promise.resolve([1, 2, 3]);
promise1.then(function(value) {
console.log(value);
// expected output: Array [1, 2, 3]
});
4. Promise原型对象的属性与方法
我们用Object.getOwnPropertyNames()方法获取Promise原型对象的所有属性与方法。
Object.getOwnPropertyNames(Promise.prototype);
// (4) ["constructor", "then", "catch", "finally"]
发现一共有4个属性和方法。
4.1 Promise原型对象的属性
Promiset.prototype.constructor
指向构造函数Promise
4.2 Promise原型对象的方法
Promise.prototype.then(onFullfilled, onRejected)
它最多需要有两个参数:Promise 的接受(fulfillment)和拒绝(rejection)情况的回调函数。返回一个新的 Promise,该Promise将以回调的返回值来resolve。
语法:
p.then(onFulfilled, onRejected);
p.then(function(value) {
// fulfillment
}, function(reason) {
// rejection
});
参数:
- onFulfilled
当Promise变成接受状态(fulfillment)时,该参数作为回调函数被调用。该函数有一个参数,即接受的值(the fulfillment value)。 - onRejected
当Promise变成拒绝状态(rejection )时,该参数作为回调函数被调用。该函数有一个参数,即拒绝的原因(the rejection reason)。
返回值:
then方法返回一个Promise。而它的行为与then中的回调函数的返回值有关:
如果then中的回调函数返回一个值,那么then返回的Promise将会成为接受状态,并且将返回的值作为接受状态的回调函数的参数值。
如果then中的回调函数抛出一个错误,那么then返回的Promise将会成为拒绝状态,并且将抛出的错误作为拒绝状态的回调函数的参数值。
如果then中的回调函数返回一个已经是接受状态的Promise,那么then返回的Promise也会成为接受状态,并且将那个Promise的接受状态的回调函数的参数值作为该被返回的Promise的接受状态回调函数的参数值。
如果then中的回调函数返回一个已经是拒绝状态的Promise,那么then返回的Promise也会成为拒绝状态,并且将那个Promise的拒绝状态的回调函数的参数值作为该被返回的Promise的拒绝状态回调函数的参数值。
如果then中的回调函数返回一个未定状态(pending)的Promise,那么then返回Promise的状态也是未定的,并且它的终态与那个Promise的终态相同;同时,它变为终态时调用的回调函数参数与那个Promise变为终态时的回调函数的参数是相同的。
注意:
如果忽略针对某个状态的回调函数参数,或者提供非函数 (nonfunction) 参数,那么 then 方法将会丢失关于该状态的回调函数信息,但是并不会产生错误。如果调用 then 的 Promise 的状态(fulfillment 或 rejection)发生改变,但是 then 中并没有关于这种状态的回调函数,那么 then 将创建一个没有经过回调函数处理的新 Promise 对象,这个新 Promise 只是简单地接受调用这个 then 的原 Promise 的终态作为它的终态。
用法示例:
var promise1 = new Promise(function(resolve, reject) {
resolve('Success!');
});
promise1.then(function(value) {
console.log(value);
// expected output: "Success!"
});
Promise.catch(onRejected)
添加一个拒绝(rejection) 回调到当前 promise, 返回一个新的promise。当这个回调函数被调用,新 promise 将以它的返回值来resolve。它的行为与调用Promise.prototype.then(undefined, onRejected) 相同。
语法:
p.catch(onRejected);
p.catch(function(reason) {
// 拒绝
});
参数:
- onRejected
当Promise 被拒绝时,被调用的一个Function。该函数拥有一个参数: - reason
拒绝的原因。
返回值:
一个Promise。
示例:
有三种常见的使用情况:
- 使用链式语句的 catch方法:
var p1 = new Promise(function(resolve, reject) {
resolve('Success');
});
p1.then(function(value) {
console.log(value); // "成功!"
throw 'oh, no!';
}).catch(function(e) {
console.log(e); // "oh, no!"
}).then(function(){
console.log('after a catch the chain is restored');
}, function () {
console.log('Not fired due to the catch');
});
// 以下行为与上述相同
p1.then(function(value) {
console.log(value); // "成功!"
return Promise.reject('oh, no!');
}).catch(function(e) {
console.log(e); // "oh, no!"
}).then(function(){
console.log('after a catch the chain is restored');
}, function () {
console.log('Not fired due to the catch');
});
- 捕获抛出的错误
// 抛出一个错误,大多数时候将调用catch方法
var p1 = new Promise(function(resolve, reject) {
throw 'Uh-oh!';
});
p1.catch(function(e) {
console.log(e); // "Uh-oh!"
});
// 在异步函数中抛出的错误不会被catch捕获到
var p2 = new Promise(function(resolve, reject) {
setTimeout(function() {
throw 'Uncaught Exception!';
}, 1000);
});
p2.catch(function(e) {
console.log(e); // 不会执行
});
// 在resolve()后面抛出的错误会被忽略
var p3 = new Promise(function(resolve, reject) {
resolve();
throw 'Silenced Exception!';
});
p3.catch(function(e) {
console.log(e); // 不会执行
});
- 如果已决议
//创建一个新的 Promise ,且已决议
var p1 = Promise.resolve("calling next");
var p2 = p1.catch(function (reason) {
//这个方法永远不会调用
console.log("catch p1!");
console.log(reason);
});
p2.then(function (value) {
console.log("next promise's onFulfilled"); /* next promise's onFulfilled */
console.log(value); /* calling next */
}, function (reason) {
console.log("next promise's onRejected");
console.log(reason);
});
Promise.prototype.finally(onFinally)
添加一个事件处理回调于当前promise对象,并且在原promise对象解析完毕后,返回一个新的promise对象。回调会在当前promise运行完毕后被调用,无论当前promise的状态是完成(fulfilled)还是失败(rejected)
注意:
finally() 虽然与 .then(onFinally, onFinally) 类似,它们不同的是:
- 调用内联函数时,不需要多次声明该函数或为该函数创建一个变量保存它。
由于无法知道promise的最终状态,所以finally的回调函数中不接收任何参数,它仅用于无论最终结果如何都要执行的情况。 - 与Promise.resolve(2).then(() => {}, () => {}) (resolved的结果为undefined)不同,Promise.resolve(2).finally(() => {}) resolved的结果为 2。
- 同样,Promise.reject(3).then(() => {}, () => {}) (resolved 的结果为undefined), Promise.reject(3).finally(() => {}) rejected 的结果为 3。
用法示例:
一个典型的用法,在发出请求时,页面的loading效果开启,然后不管返回的结果是完成(fulfilled)还是失败(rejected),都会执行onFinally将loading效果取消。
let isLoading = true;
fetch(myRequest).then(function(response) {
var contentType = response.headers.get("content-type");
if(contentType && contentType.includes("application/json")) {
return response.json();
}
throw new TypeError("Oops, we haven't got JSON!");
})
.then(function(json) { /* process your JSON further */ })
.catch(function(error) { console.log(error); })
.finally(function() { isLoading = false; });
- Promise实例对象的属性与方法
我们用Object.getOwnPropertyNames()方法获取Promise实例对象的所有属性与方法。
var p = new Promise(function(resolve, reject) {
resolve('success');
})
Object.getOwnPropertyNames(p); // []
我们发现实例本身没有绑定属性与方法。