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); // 失败
备注
春招开始了,有点不敢投,踌躇着,也焦虑着。