简介
这段时间公司上线了个印尼的项目,通过 CMD
+ seajs
+ 模版来实现的一个单页应用,然后经过压缩打包,跟起 BUG 来那种痛苦简直是: 不想说,就对了,一万个nnnnnnnnnnnnnm 。
吐槽归吐槽,现在前端模块化,工程化开发越来越流行,只能说公司还停留在原生硬编程阶段是比较落后的。这个代码还是有很多值得学习的地方的(哪个厂家的就不透露了,你懂的~~~)。
比如今天碰到的跟 Promise
有关的,代码里面可以说大量使用了 Promise
,根据这几天的跟进,对 Promise
这个也更加熟悉了点,这里就来进一步的学习,记录下。
言归正传,进入正题 ✈
Promise 对象
顾名思义,从 Promise
的英文字面意思“承诺”来理解,意思就是它会确保在满足条件的情况下一定会去执行相关的逻辑,其实就是我们的异步任务。
语法:
new Promise(function (resolve, reject) { ... });
参数说明:
resolve
:代表承诺已经实现会被执行的函数,这个函数可以在接下来的 .then()
中第一个参数去指定;
reject
:代表承诺没法实现,被拒绝的状态,则会执行该函数,这个函数是可选项,如果没指定而状态又是reject
那便会被后面的.catch
捕获处理;
来看一个简单的小示例:(承诺:下午三点钟见)
// promise/index.html
var $ = (id) => document.getElementById(id),
debug = (str) => console.log(str);
var isSuccess = true;
new Promise((resolve, reject) => {
isSuccess ? resolve('下午三点见!') : reject('今天行程已满,下次见吧!');
}).then(msg => debug(msg), msg => alert(msg));
上面代码的执行结果由 isSuccess
来决定,如果承诺可以见面,那么便会执行 resolve
而在控制台输出 :
如果不能兑现承诺便会 alert
出: ‘今天行程已满,下次见吧!’
通过上面简单的是否见面的示例演示了如何使用 Promise
;
了解了 Promise
对象的简单使用,下面看下其常用的几个函数如何使用
Promise 函数
then(resolveFn[, rejectFn]);
这个函数用来创建承诺成功或失败后执行的函数(resolveFn
, rejectFn
),承诺对象的回调函数里面的两个参数即这个 then
里面对应的函数;
简单示例如上面的‘见面示例’。
另外,.then()
语法支持点式链式语法,比如:promise.then().then().then();
前提是 then()
前面的 then()
必须返回一个 Promise
对象,否则都会按照承诺兑现来执行,也就是会执行 resolve
函数。
示例:(下午三点见! -> 商讨合作事宜) 两个事务
// promise/index.html
var isSuccess = true;
new Promise((resolve, reject) => {
isSuccess ? resolve('下午三点见!') : reject('今天行程已满,下次见吧!');
})
.then(msg => {
debug(isSuccess + ': ' + msg);
}, msg => alert(isSuccess + ': ' + msg))
.then(() => console.log('then 2 resolve'), () => alert('then 2 reject'))
如果只是上面形式,第一个 .then()
中并没有返回 Promise
对象,不管第一个.then
结果是 resolve
还是 reject
,后面的then
执行的永远是第一个参数中的回调。
即:控制台在 then 1
承诺成功或失败后总是输出 :’then 2 resolve’
修正:
.then(msg => {
debug(isSuccess + ': ' + msg);
// 这里返回一个新的 Promise 对象,供紧跟在后面的 then 使用
return new Promise((resolve, reject) => {
resolve('商讨合作事宜');
});
}, msg => alert(isSuccess + ': ' + msg))
.then((msg) => debug(msg), () => alert('then 2 reject'))
经过上面的修正之后,在 isSuccess
为 true
的时候,表示有时间见面并决定会面所谈事务
输出结果:
从结果可知,第一行是由第一个 .then
承诺成功输出,表示承诺见面,第二行也就是由第二个 .then
的承诺成功的输出,表示要商谈的内容。
使用 .then
的时候只要记住,如果要链式重复使用 then
,就要确保前一个 then
中返回的也是个 Promise
对象。
catch( errorHandler )
或 catch( rejectHandler )
catch
异常捕获函数,如果Promise
对象后面调用了 catch
,那么前面所有函数中的异常都不会被该函数给捕获
除了异常捕获之外,如果.then
中没有指定第二个函数,即 reject
处理,那么当承诺失败时,会默认去执行 catch
,或者理解为承诺失败也属于异常情况。
分别看下两个例子
发生异常或错误:(比如:严格模式下变量未声明就直接使用)
// promise/index.html
'use strict';
var isSuccess = true;
new Promise((resolve, reject) => {
a = 19; // 这里未声明直接使用
isSuccess ? resolve('下午三点见!') : reject('今天行程已满,下次见吧!');
})
.then(msg => {
debug(isSuccess + ': ' + msg);
})
.catch(e => alert(e));
假如没有错误发生,或者没有 catch
情况下,会执行 resolve('下午三点见!')
,而上例代码结果会是:
但是有点奇怪的是,尝试把 a = 19;
这行移到resovle
和 reject
之后
isSuccess ? resolve('下午三点见!') : reject('今天行程已满,下次见吧!');
a = 19; // 这里未声明直接使用
会惊奇的发现并不会弹出上面的报错框,而是控制台正常输出了 ‘true: 下午三点见!’(搜了下相关的解释,不是很理解,记住先吧)。
把 a = 19;
去掉,isSuccess
改成 false
,那么这个时候的会执行 reject
可是then
中又没有传入第二个参数,导致 Promise
中的 reject
参数是undefined
,因此执行reject('今天行程已满,下次见吧!');
也就会出现问题了,并且会被catch
捕获到
所以上面✈的关于reject
和catch
的说法不太准确,这里更正一下,.then
中没有传入reject
承诺失败的情况还是因为错误导致的,被catch
给捕获了。
可以把catch
去掉验证,看控制台输出结果如下:
Promise.all(iterable)
iterable
:是一组Promise
对象的集合,这个集合会被当作后面的.then
中函数的参数。
all
函数的描述是,只有在 iterable
中所有的Promise
对象执行结果是 resovle
的时候才会执行后面.then
中的 resolve
,要是其中有一个出现reject
那么就会执行.then
中的reject
。
通俗点讲就是:你们(集合)的承诺全部兑现,我(.then
)才会兑现承诺,否则只要有一个没有兑现,那么我也就不会兑现我的承诺。
示例:
// promise/index.html
var p1 = new Promise(function (resolve, reject) {
resolve('p1-resolve');
});
var p2 = new Promise(function (resolve, reject) {
resolve('p2-resolve');
// reject('p2-reject');
});
Promise.all([p1, p2]).then(ps => alert('resolve: ' + ps), ps => alert('reject: ' + ps));
全部resolve
,上面输出结果将会是:
如果有一个reject
resolve('p2-resolve');
// reject('p2-reject');
那么结果是:
如果说再加入一个 p3-reject
,并且放在 p2-reject
之前
var p3 = new Promise(function (resolve, reject) {
reject('p3-reject');
});
结果会是:
这说明 Promise.all.then
只要遇到第一个 reject
就会结束,然后去执行then
中reject
,其实这个跟逻辑或和与有点类似,逻辑与:必须全部为真最后结果才真,如果有一个未假后面就不会再执行。
另外会发现,all
中的 iterable
集合中的值,是由所有all
里面的resovle
参数组合而成,然后整体传递给.then
的resolve/reject
函数。
那假如直接使用基本数据呢:
// promise/index.html
var p1 = 'Hello', p2 = 'World', p3 = '!';
Promise.all([p1, p2, p3]).then(ps => alert('resolve: ' + ps), ps => alert('reject: ' + ps));
得到的结果会输出:Hello,World,!
其实就是把 [p1, p2, p3]
数组字符串化输出了。
所以,不过你传入all
的类型是什么,它都会把其组合成数组传给.then
的函数;
比如
对象:var p1 = 'Hello', p2 = {name:'lizc'}, p3 = '!';
结果: resolve: Hello,[object Object],!
数组:var p1 = 'Hello', p2 = [1,2,3], p3 = '!';
结果: resolve: Hello,1,2,3,!
综上所述
all
函数的 iterable
中
包含的如果是
Promise
对象,那么数据会采用Promise
中调用resolve
或reject
时传入的参数包含的如果是基本数据类型,那么会以数组成员组合到
iterable
中,如:Hello,World,!
包含的如果有对象,那么会将对象
toString
,即得到[object Object]
,作为数组成员,如:resolve: Hello,[object Object],!
包含的如果是数组,则会将数组拆分出成员然后和
all
的参数数组进行组合成新的数组,如:resolve: Hello,1,2,3,!
并且all
参数不论以何种形式组合,都遵循:只要有一个reject
就会then
中reject
,只有全部resolve
才会then
中resolve
,而基本数据类型和对象类型会被认为是resolve
;
因此,当
var p1 = 'hello', p2 = 'world', p3 = new Promise(function (resolve,reject) { reject('p3-reject'); });
的时候,结果会是 reject: p3-reject
;
Promise.race(iterable)
使用:
Promise.race([p1, p2]).then();
race
可以理解为竞争,其后的then
执行结果是resolve
还是reject
,取决于[p1, p2]
那个先执行完。
即:如果 p1
先执行完成为resolve
,p2
后执行完为reject
,那么race.then
中执行resolve
。反之,执行p2
中的reject
。
看个示例:(谁先走问题)
// promise/index.html
var p1 = new Promise(function (resolve, reject) {
setTimeout(resolve, 500, 'p1 first');
});
var p2 = new Promise(function (resolve, reject) {
setTimeout(resolve, 1000, 'p2 first');
});
var p3 = new Promise(function (resolve, reject) {
setTimeout(resolve, 1500, 'p3 first');
});
Promise.race([p1, p2, p3]).then(ps => alert('resolve: ' + ps), ps => alert('reject: ' + ps));
上例结果:p1 first
这个 race
并不难理解,只要那个任务先执行完就会根据任务承诺结果,去执行对应的race.then
中的reject
或resolve
;
Promise 继承自 Function 的属性和函数
继承自 Function
需要注意的是 bind
函数,经常会被 Promise
模式中使用到。
总结
对于 Promise
对象下面的主要也就 then
, catch
, all
, race
, 另外就是 resolve
和 reject
这几个函数的使用和结合使用的情况。
最后简单补充下 resolve
和 reject
的使用:
Promise.resolve(value)
这个函数效果就是,将
value
转换成一个承诺兑现的Promise
对象,其实就是必定执行resolve
的Promise
对象。比如:
var p = Promise.resolve('hello world'); p.then(value => alert(value)); // => hello world
如果参数
value
值是普通类型,会直接将其转成结果为resolve
的Promise
对象;如果参数是个带有
then
函数的对象,那么会根据then
中的承诺结果来执行,比如:var p = Promise.resolve({ then: function (success, fail) { success('object with then, success'); } }); p.then(value => alert(value)); // 结果:object with then, success
从实例结果可知
p.then
会把Promise.resolve()
里面的then
中的success/fail
参数传递给p.then
回调的参数。Promise.reject(value)
reject
使用方法和resolve
原理一样Promise .reject(throw new Error('rejected, ugly so much !')) .then(function (error) { // 这里是 resolve,不会执行 }, function ( error ) { // 执行这里 reject alert(error) }); // 结果:rejected, ugly so much !(因为太丑而被拒绝)
Promise
对象小研究,到此告终,颇有收获,写着写着感觉自己码字速度也快了不少(撸啊撸,手酸着~~)。
其实对于异步任务执行 ES7 中还更新了 Generator
和 async
函数,更加智能好用,有兴趣值得学习学习。