JS: Promise
Promise
Promise
是 JavaScript 异步编程中的重要概念,是目前较为流行的 JavaScript 异步编程解决方案之一。它最早由社区提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise
对象。
Promise 的基本概念
一个 Promise
有以下几种状态:
- pending: 初始状态,既不是成功,也不是失败状态(也可以说是进行中)。
- fulfilled: 意味着操作成功完成。
- rejected: 意味着操作失败。
pending 状态的 Promise 对象可能触发 fulfilled 状态并传递一个值给相应的状态处理方法,也可能触发失败状态(rejected)并传递失败信息。当其中任一种情况出现时,Promise 对象的
then
方法绑定的处理方法(handlers )就会被调用(then方法包含两个参数:onfulfilled 和 onrejected,它们都是 Function 类型。当Promise状态为 fulfilled 时,调用 then 的 onfulfilled 方法,当 Promise 状态为 rejected 时,调用 then 的 onrejected 方法, 所以在异步操作的完成和绑定处理方法之间不存在竞争)。 ------MDN
因为Promise.prototype.then
和 Promise.prototype.catch
方法会返回 promise 对象,所以它们可以被链式调用。
Promise 的基本用法
// 这是 Promise 的常用方法
function promise(...) {
return new Promise((resolve, reject) => {
// do something
if (/* fulfilled (异步操作成功) */) {
return resolve(value);
}
reject(error);
});
}
promise(...).then((value) => {
// success
}, (error) => {
// failure
});
Promise
构造函数接受一个函数作为参数,该函数的两个参数分别是resolve
和reject
。它们是两个函数,由 JavaScript 引擎提供。
当状态从 pending
变为fulfilled
时,即异步操作成功,这时调用resolve
,并将异步操作的结果,作为参数传递出去。当状态从 pending
变为rejected
时,即异步操作失败,这时调用reject
,并将error
作为参数传递出去。
Promise
实例生成后,通过then
方法部署fulfilled
状态和rejected
状态的回调函数。
下面是一个用Promise
封装 Ajax 的例子:
const getJSON = function({
// 解构赋值设置默认参数
url = 'url',
method = 'GET',
async = false,
data = null
} = {}) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open(url, method, async);
xhr.responseType = 'json';
xhr.setRequestHeader('Accept', 'application/json');
xhr.onreadystatechange = function() {
if (this.readyState == 4) {
let status = this.status;
if (status >= 200 && status < 300 || status == 304) {
resolve(this.response);
} else {
reject(this.statusText);
}
}
}
xhr.send(data);
});
}
getJSON({url: '/json'})
.then(json => console.log(json), error => console.log(error));
Promise 的链式调用
以上面封装的 Ajax 为例子:
getJSON({url: '/json'})
.then( json => getJSON({url: json.oneUrl}) )
.then( json => getJSON({url: json.twoUrl}) )
.catch( error => {...} )
.then( () => {...} );
因为then
或者catch
方法会返回一个Promise
对象,或者自己return
一个Promise
对象,而这样就可以继续使用then
或者catch
方法。
Promise.prototype.catch()
这里的catch()
与try/cath
的方法类似,都是捕捉错误进行处理。
// 这两种写法是等价的
// 第一种
getJSON({url: '/json'}).then( (json) => {
// success
}).catch( (error) => {
// failure
});
// 第二种
getJSON({url: '/json'}).then((json) => {
// success
},(error) => {
// failure
});
但比较建议使用第一种写法,因为使用catch
让代码更接近于同步代码,而且在链式调用中,使用catch
方法可以捕捉前面所有的错误进行一并处理。
Promise
对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止,而且catch
还会返回一个Promise
对象,这样可以继续使用then
方法进行链式调用。
getJSON({url: '/json'})
.then( json => getJSON({url: json.oneUrl}) )
.then( json => getJSON({url: json.twoUrl}) )
.then( json => {...} )
.catch( error => {
// 这里可以处理前面所有 Promise 的错误
}).then( () => {...} );
Promise.prototype.finally()
finally
里面的操作不管 Promise 对象最后状态如何,都是会去执行的,这和 JAVA 的 finally 有点相似,即无论如何,一定执行。
promise(...)
.then(result => {···})
.catch(error => {···})
.finally(() => {
// 这里的操作一定执行
});
值得注意的是,finally
方法的回调函数是不接受任何参数的,这意味着没有办法知道前面的 Promise 状态到底是fulfilled
还是rejected
。所以finally
方法里面的操作,应该是与状态无关的,不依赖于 Promise 的执行结果。
Promise.resolve()
Promise.resolve(value)
方法返回一个以给定值解析后的promise
对象。但如果这个值是个 thenable(即带有then方法),返回的promise会“跟随”这个thenable的对象,采用它的最终状态(指resolved/rejected/pending/settled);
如果传入的value本身就是promise对象,则该对象作为Promise.resolve方法的返回值返回;
否则以该值为成功状态返回promise对象。
-
传入参数是一个
promise
对象如果传入参数本身就是 Promise 实例,那么
Promise.resolve
将不做任何修改、原封不动地返回这个实例。const p0 = new Promise((resolve, reject) => { resolve(1); }); const p1 = Promise.resolve(p0); console.log(p1 === p0); // true
-
传入参数是
thenable
对象Promise.resolve()
会将这个对象转为 Promise 对象,然后立即执行thenable
对象的then
方法,执行完毕之后,Promise 对象状态变为fulfilled
状态,然后就会执行后续的then
方法。// thenable 即带有 then 方法的对象 let thenable = { then: (resolve, reject) => { resolve(1); } }; const p2 = Promise.resolve(thenable); p2.then( value => console.log(value) );
-
传入参数为其他值
如果参数不为前面的两种的情况,则
Promise.resolve
方法会以fulfilled
状态返回一个新的 Promise 对象,并将传入的参数传给then
方法。const p3 = Promise.resolve(123); p3.then( value => console.log(value) ); // 123
Promise.reject()
Promise.reject(value)
会以rejected
状态返回一个 Promose 实例,并将传入参数原封不动地传给then
或者catch
方法。
const p4 = Promise.reject('错误');
// then()
p4.then(null, (err)=>{
console.log(err); // 错误
});
// catch()
p4.catch( err => console.log(err) ); // 错误
Promise.all()
Promise.all(iterable)
方法返回一个Promise
实例,此实例在iterable
参数内所有的promise
状态都为fulfilled
状态(即成功)或参数中不包含promise
时回调完成;如果参数中promise
有一个状态为rejected
状态(即失败)时回调失败,失败的原因是第一个失败promise
的结果。
一般来说,Promise.all
方法会传入一个数组参数,如果数组成员里有非Promise
实例时,会自动调用上面所说的Promise.resolve
方法将其转为Promise
实例,再进行处理。
然后处理结果分两种情况:
1.参数内所有Promise
实例的状态都为fulfilled
状态,则返回一个包含所有resolve
结果的数组传给后续的回调函数。
2.参数内有一个Promise
实例的状态为rejected
状态,则将第一个被reject
的Promise
实例的结果传给后续的回调函数。
const p5 = Promise.resolve(123);
const p6 = 42;
const p7 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'foo');
});
Promise.all([p5, p6, p7]).then((values) => {
console.log(values); // [123, 42, "foo"]
});
Promise.race()
Promise.race
方法与上面的Promise.all()
类似,但不同的是,Promise.race()
是只要传入的Promise
实例中有一个状态改变了,就会直接返回这个最快改变状态的Promise
实例的结果,而不是返回所有。
const p8 = Promise.resolve(123);
const p9 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'foo');
});
Promise.race([p8, p9]).then((values) => {
// 这里只会获得 p8 的返回值
console.log(values); // 123
});
手动实现 Promise.all()
有的面试会让实现Promise.all()
方法,这个还算简单的,有的还要手撕。Promise
首先来理清一下实现思路:
-
遍历执行传入的
Promise
实例。 -
如果传入的参数成员有不是
Promise
实例的,会直接调用Promise.resolve()
进行处理。 -
返回一个新的
Promise
实例,分两种情况返回结果。 -
fulfilled
状态返回的数组结果要按顺序排列。
实现代码如下:
function promiseAll(promises) {
// 返回一个新的 Promise 实例
return new Promise((resolve, reject) => {
let count = 0;
let promisesNum = promises.length;
let resolvedValue = new Array(promisesNum);
for (let i=0; i<promisesNum; i++) {
// 用 Promise.resolve() 处理传入参数
Promise.resolve(promises[i])
.then((value) => {
// 顺序排列返回结果
resolvedValue[i] = value;
if (++count == promisesNum) {
return resolve(resolvedValue);
}
})
.catch((err) => {
// 返回第一个 rejected 状态的结果
return reject(err);
});
}
});
}
测试如下:
// 成功的情况
const p1=Promise.resolve(1);
const p2=Promise.resolve(2);
const p3=Promise.resolve(3);
promiseAll([p1, p2, p3]).then(console.log); // [1, 2, 3]
// 失败的情况
const p1=Promise.resolve(1);
const p2=Promise.reject('失败');
const p3=Promise.resolve(3);
promiseAll([p1, p2, p3])
.then(console.log)
.catch(console.log); // 失败
备注
春招开始了,有点不敢投,踌躇着,也焦虑着。