Promise
是异步编程的解决方案之一,相比传统的回调和事件机制更为合理和强大。编程
某天,突发奇想,发了封邮件给木匠师傅,定制一个如此这般的家具。
木匠有求必应,便是说,邮件一旦发出就获得了他的承诺(Promise
):在下必定尽力。数组
邮件中规定好告终果的通知方式:
成功了,直接将家具(res
)邮递(resolve
)过来。
失败了,直接将失败的信息(err
)发邮件(reject
)过来。并发
let P = new Promise((resolve, reject) => { if (/*最终的结果*/) { resolve('家具'); // 成功,直接邮递家具。 } else { reject('失败的缘由'); // 失败,发邮件告知失败缘由。 } });
邮件发出等价于获得木匠的承诺P
,以后,能作的只有等待(then
)。异步
P.then(res => { console.log('成功,收到家具。此刻心情:开心。'); }, err => { console.log('失败,收到缘由。此刻心情:失落。'); });
每一个Promise
有三种状态:进行中(pending
)、已成功(resolved
)和已失败(rejected
)。
建立即进入pending
状态,在传入方法中一旦调用了resolve/reject
方法,最终状态便变成resolved/rejected
。
一旦变成结果状态,即更改为resolved/rejected
,状态便被冷冻,不能再被更改。async
状态容器 Promise
实质是个状态容器。
获得结果状态后,任什么时候候均可以访问到此状态。
这与事件订阅通知不一样,若是订阅发生在通知以后,订阅是不起做用的。异步编程
状态不可控
一旦建立Promise
,便会马上执行,没法取消。
处于pending
状态时,没法得知进程具体的信息,好比完成百分比(虽然能够自行设置回调进行通知)。函数
失败的状态
成功的状态只能由resolve
方法转成。
失败的状态能够由reject
方法转成,也能够由抛出错误间接转成。code
三者都会正常的打印出失败的信息。 new Promise((resolve, reject) => { reject('error'); }).catch(console.log); // error new Promise((resolve, reject) => { a; }).catch(console.log); // ReferenceError: a is not defined new Promise((resolve, reject) => { throw 'error'; }).catch(console.log); // Error: error
错误的报告机制
若是失败状态没有接收失败的回调函数接收,Promise
会抛出错误。
这里的抛出错误,仅仅是在控制台显示之类的提示,不会终止程序的进程。orm
先打印出 'err' ,再报错。 new Promise((resolve, reject) => { reject(); }); new Promise((resolve, reject) => { reject('err'); }).then(() => {}, console.log);
一旦Promise
设置了失败回调函数,即使是代码执行错误,也会自行消化,不外报。对象
虽然 a 未被定义,但全程安静,无槽点。 new Promise((resolve, reject) => { a; }).then(() => {}, () => {});
传入方法
建立Promise
的同时也会执行传入方法。
传入方法不会由于调用了resolve/reject
便终止执行,因此更优的方式是retrun resolve/reject
。
打印出 1 2 。 new Promise((resolve, reject) => { console.log(1); resolve(); console.log(2); });
回调方法
当即获得结果的Promise
,其回调函数依然会晚于本轮事件执行。
这种后执行不一样于setTimeout
的将执行函数push
到执行栈,而是将执行函数放到本轮的末尾。
获得的结果是:1 2 3 4 5 6 。 console.log(1); let p = new Promise((resolve, reject) => { console.log(2); resolve(); }); setTimeout(() => { console.log(5); }); p.then(function() { console.log(4); }); setTimeout(() => { console.log(6); }); console.log(3);
传入reject
的参数,通常是字符串或Error
实例,表示抛出的错误。
传入resolve
的参数,通常是相应的JSON
数据等,表示获得的数据。
传入resolve
的参数,还能够是另外一个Promise
实例。
这时,只有当内层的Promise
结束后,外层的Promise
才会结束。
过两秒后,打印出 2000 。 new Promise((resolve, reject) => { resolve(createPromise()); }).then(console.log); function createPromise() { return new Promise((resolve, reject) => { setTimeout(() => { resolve(2000); }, 2000); }); }
在这种状况下,若是内层失败,并不等于传递Error
实例给resolve
不一样。
前者是内层Promise
抛出了错误将被外层捕获,后者仅仅是参数为一个Error
实例。
内层失败的信息,被外层捕获。过两秒,打印出 '2' 2000 。 new Promise((resolve, reject) => { resolve(createPromise()); }).then(res => { console.log('1', res); }, err => { console.log('2', err); }); function createPromise() { return new Promise((resolve, reject) => { setTimeout(() => { reject(2000); }, 2000); }); }
该方法可传入两个,分别对应成功/失败时的回调函数。
该方法返回的是一个新的Promise
对象,这也是可使用链式(.then.then...
)的缘由。
let p1 = new Promise(resolve => resolve(2000)); let p2 = p1.then(() => {}, () => {}); console.log(p1 === p2); // false
return
链式中,后者的状态取决于前者(成功/失败)的回调函数中返回(return
)的结果。
若是没有返回,至关返回一个成功的状态,值为undefined
。
若是返回为Promise
对象,后者的状态由该对象的最终状态决定。
若是返回为非Promise
对象的数据,至关返回一个成功的状态,值为此数据。
若是前者执行时抛出了错误,至关是返回一个失败的状态,值为此错误。
依次打印出: 1 res 2000 2 res undefined 3 res 3000 4 err 4000 new Promise(resolve => resolve(2000)) .then(res => { console.log('1 res', res); }) .then(res => { console.log('2 res', res); return 3000; }) .then(res => { console.log('3 res', res); return new Promise((resolve, reject) => { reject(4000); }); }) .then(console.log, err => { console.log('4 err', err); });
状态的传递
在链式中,若是前者的状态没有被后者捕获,会一直(像)冒泡到被捕获为止。
状态被捕获后便消失,这以后的的状态由当前then
返回的状态决定,以后重复。
依次打印出: 2 res 2000 3 res 3000 new Promise(resolve => resolve(2000)) .then(null, err => { console.log('1 err', err); }) .then(res => { console.log('2 res', res); return 3000; }) .then(res => { console.log('3 res', res); });
用于指定发生错误时的回调函数,等价于:.then(null, callback)
。
其表现与then
一致,好比返回新的Promise
,状态的继承和传递等等。
通常推荐使用catch
而不是then
的第二个方法接收错误。
由于catch
能够捕获then
自身的错误,也更接近同步的写法(try/catch
)。
new Promise(() => {}) .then(() => { ... }) .catch(() => { ... });
用于Promise
处理结束后的收尾工做。
传入其的回调函数不会接受任何参数,意味着没有办法知道Promise
的结果。
这也正代表,finally
里面的操做与状态无关,不依赖Promise
的处理结果。
其本质和catch
同样,也是then
方法的变种。
不过其仅仅是状态的传递者,只会返回原状态,不会接收状态和建立新的状态。
p.finally(() => { // codes... }); --- 等价于 p.then(res => { // codes... return res; // 将原成功状态返回 }, err => { // codes... throw err; // 将原失败状态返回 });
示例
在请求数据时,咱们会显示加载图案,请求完成后不管结果都要隐藏此图案。
通常,一个完整的 Promise 的结构会以下。 showLoading = true; new Promise((resolve, reject) => { // 请求... }) .then(res => { // 成功处理... }) .catch(err => { // 失败处理... }) .finally(() => { // 重置一些状态... showLoading = false; });
此方法直接返回一个状态为resolved
,值为其参数的Promise
。
Promise.resolve(res); --- 等价于 new Promise(resolve => resolve(res));
此方法直接返回一个状态为rejected
,值为其参数的Promise
。
Promise.reject(res); --- 等价于 new Promise((resolve, reject) => reject(res));
此方法用于将多个Promise
实例,包装成一个新的Promise
实例。
其参数为一个数组,每一项应为Promise
实例(不是则会使用Promise.resolve
进行转化)。
新Promise
的状态取决于传入数组中的每一项的最终状态。
若是有一项状态变成rejected
,新实例则为rejected
,值为该项的返回值。
若是所有项都变成了resolved
,新实例则为resolved
,值为包含每一项返回值的数组。
三秒后,打印出:[1, 2, 3]。 let pArr = [1, 2, 3].map(createPromise); Promise.all(pArr).then(console.log); function createPromise(num) { return new Promise((resolve, reject) => { setTimeout(() => { resolve(num) }, num * 1000); }); }
此方法与all()
基本相同,传入的参数也是一个Promise
数组。
不一样的是,新Promise
的最终状态是由数组中第一个状态改变的项(成功或失败)决定的。
一秒后,打印出 1 。 let pArr = [1, 2, 3].map(createPromise); Promise.race(pArr).then(console.log); function createPromise(num) { return new Promise((resolve, reject) => { setTimeout(() => { resolve(num) }, num * 1000); }); }
在实际项目中,有时须要处理多个相互关联的异步脚本(多为数据请求)。 ES6
以后async
函数应该是最灵活方便的途径,Promise
在其中扮演基石的角色。
不过在这一小节,依旧会以Promise
做为主要的解决办法进行分析。
这里是下面须要用到的共同方法。
// 建立异步。 function createPromise(name) { return new Promise(resolve => { setTimeout(() => resolve({ [name]: `Data form ${name}` }), 1000); }); } // 异步 A, B, C。 function A(param) { return createPromise('A'); } function B(param) { return createPromise('B'); } function C(param) { return createPromise('C'); } // 并发处理多个独立的异步请求。 function dealIndependentRequests(qArr, callback) { return new Promise((resolve, reject) => { let done = false; let resData = []; let leftNum = qArr.length; qArr.forEach((q, i) => { Promise.resolve(q).then(res => { !done && dealRequest(res, i, true); }).catch(err => { !done && dealRequest(err, i, false); }); }); function dealRequest(res, index, isSuccess) { if (callback) { done = callback(resData, res, index, isSuccess); } else { resData[index] = { res: res, isSuccess: isSuccess }; } if ( done || !(--leftNum) ) resolve(resData); } }); }
5.1.1
有三个请求数据的异步:A, B, C。
最终的数据必须同时结合三者的数据计算得出。
基于要求,直接使用Promise.all
进行并发请求,等到全部信息到齐后结束。
大概一秒后,打印出:Get all data: [{...}, {...}, {...}]。 Promise.all([A(), B(), C()]) .then(res => { console.log(`Get all data:`, res); }) .catch(err => { console.error(err); });
5.1.2
有三个请求数据的异步:A, B, C。
最终的数据必须同时结合A, B的数据计算得出,C只是修饰数据。
基于要求,使用Promise.all
并发A, B请求,成功后再发C。
若是前者成功,再看C是否成功,以后使用不一样方式处理获得最终数据。
大概两秒后,打印出:[{…}, {…}] {C: "Data form C"}。 Promise.all([A(), B()]) .then(res => { C().then(c => { console.log(res, c); }) .catch(err => { console.log(res); }); }) .catch(err => { console.error(err); });
5.1.3
有三个请求数据的异步:A, B, C。
最终的数据必须基于结合A的数据计算得出,B, C起独立的修饰做用。
基于要求,与上面的处理基本相同。
不过要在A的回调里同时请求B, C,并使用状态控制变量控制程序的进程。
大概两秒后,打印出:End {A: "Data form A"} [{…}, {…}]。 A() .then(res => { dealIndependentRequests([B(), C()]) .then(subs => { console.log('End', res, subs); }) .catch(err => { console.log('End', res); }); }) .catch(err => { console.error(err); });
5.2.1
有三个请求异步:A, B, C。
B的请求须要发送A中的a信息。
C的请求须要发送B中的b信息。
基于要求,必须逐步请求A, B, C,并且前二者任一出错则中止。
大概三秒后,打印出:End {C: "Data form C"}。 A() .then(res => { return B(res.a); }) .then(res => { return C(res.b); }) .then(res => { console.log('End', res); }) .catch(err => { console.log(err); });
5.2.2
有三个请求异步:A, B, C。
B的请求须要发送A中的a信息,即使A失败也须要发送。
C的请求须要发送B中的b信息。
基于要求,与前者基本相同,只是即使A失败了也会继续请求。
大概三秒后,打印出:End {C: "Data form C"}。 A() .then(res => { return B(res.a); }) .catch(err => { return B(); }) .then(res => { return C(res.b); }) .then(res => { console.log('End', res); }) .catch(err => { console.log(err); });
5.3.1
有三个请求异步:A, B, C。
须要找出全部异步结果中,包含某值的结果的集合。
基于要求,并发请求全部数据,一一验证返回符合的结果集。
大概一秒后,打印出:[{B: "Data form B"}] dealIndependentRequests([A(), B(), C()], (data, res) => { if (res.B) data.push(res); return false; }) .then(console.log) .catch(console.log);
5.3.2
有三个请求异步:A, B, C。
只须要找到一个包含某值的结果。
基于要求,仍是使用并发请求。
有任一请求符合预期时,结束并返回(暂不涉及取消请求操做)。
大概一秒后,打印出:[{B: "Data form B"}] dealIndependentRequests([A(), B(), C()], (data, res) => { if (res.B) return data.push(res); return false; }) .then(console.log) .catch(console.log);