原副标题:衡阳后端专业培训讲义习题:Promise这种认知更单纯
今天跟大家撷取下衡阳后端专业培训讲义习题:Promise这种认知更单纯。Promise阿宝怎么用?从四个故事情节已经开始吧:
您是一位在有名沉沦城市中探险的盖基。您置身于一家点缀绚丽的卧室中,四面满布了有名的雕塑和浮雕。您发现有四个地下通道依次通往不同的路径,依次是:四个邪恶的地下通道和四个光亮的地下通道。
邪恶的地下通道中充满著了谜样的韵味,您能感受到当中蕴含着的有名力量。您无人知晓它会带您去哪里,但您能感受到当中蕴含的脆弱和未明。再者,光亮的地下通道看上去凉爽宽敞,您能看到高处映照的日光。您能听到许多嘹亮的翩翩起舞,似乎在那个路径上有许多人在庆祝。
现在,您需要在这四个地下通道之间作出优先选择。您会优先选择哪四个地下通道呢?您的优先选择Sonbhadra影响您接下去的历险之旅。
先来介绍两个单字,两个Promise的名词
Promise的四个状况:pending(等候状况)、fulfilled(已获得成功状况)和 rejected(已失利状况)
Promise的四个常见反弹表达式(咱说的是常见):
示例化第一类时传至的反弹表达式 then方式的传至的四个反弹表达式 catch方式传至的四个反弹表达式以工作台为例,继续深入细致介绍Promise
已经开始写工作台,此时Promise状况为pending,我们能认知为初始状况,也能认知为业务处理中。
工作台完成情形有两种,一类是完稿了,一类是被狗吃了,不论何种情形,必须要告诉同学,获得成功了就透过resolve地下通道存贮统计数据,与此同时会更动状况为获得成功fulfilled;失利了就透过reject地下通道存贮统计数据,与此同时修正状况为rejected。
单纯来几段标识符看看
const p = new Promise(function (resolve, reject) {
resolve(透过获得成功地下通道存储统计数据)
})
//…如果不使用then,提取,则统计数据一直存贮在p第一类中
//提取结果
p.then(function (val) {
console.log(val)
})
这些写法都是固定的,建议使用promise之前一定要充分介绍反弹表达式。咱们再升级一下,写工作台用了5秒钟。
const p1 = new Promise(function (resolve, reject) {
//写工作台用了5秒钟,5秒钟之后,把获得成功的工作台存贮到resolve地下通道中
setTimeout(() => {
resolve(这是工作台)
}, 5000)
})
//上面标识符中调用了resolve的时候,then里面的反弹表达式才会触发,这里有个时间差或者时空差的感觉
p1.then(function (val) {
console.log(val)
})
再升级一下,这工作台他也有被狗吃了的时候,我们假定5秒钟之外就被狗吃了,5秒钟之内就是完成的
const p2 = new Promise(function (resolve, reject) {
//生成四个1~6秒之间的随机数
const time = Math.random() * 4000 + 2000
setTimeout(() => {
if (time <= 5000) {
resolve(获得成功交工作台啦)
} else {
reject(`工作台被狗吃了,耗费了${time秒}`)
}
}, time)
})
//获得成功使用then来接,失利了怎么拿到参数呢?
//用then的第二个参数来拿失利的参数
p2.then(function (val) {
console.log(val)
}, function (err) {
console.log(估计是被狗吃了, err)
})
除了then的第二个参数能拿到失利的结果,还能透过catch方式拿到结果,一会再讨论这两种用法的区别,先看catch的用法,注意这里需要连用
p2.then(function (val) {
console.log(val)
}).catch((reason) => {
//输出失利原因,大概率是被狗吃了
console.log(估计是被狗吃了, reason)
})
再看一类常见的连用的写法
new Promise(function (resolve, reject) {
//生成四个1~6秒之间的随机数
const time = Math.random() * 4000 + 2000
setTimeout(() => {
if (time <= 5000) {
resolve(获得成功交工作台啦)
} else {
reject(`工作台被狗吃了,耗费了${time}秒`)
}
}, time)
}).then(function (val) {
console.log(val)
}).catch((reason) => {
//输出失利原因,大概率是被狗吃了
console.log(估计是被狗吃了, reason)
})
许多需注意的地方
resolve和reject只是四个形参的名字,对应实际的值是promise内部的表达式,调用这四个其实调用的就是promise内部的某个表达式而已,这个名字能随便去改
new Promise(function (ok, fail) {
//此时此刻形参叫ok,但实际代表的是promise内部表达式
ok(ojbk)
}).then((res) => {
//promise内部会存储统计数据,传给res这个变量,此时res 值就是ojbk
console.log(res)
})
例如new Promise(构造器的参数是四个表达式),这个表达式会同步执行,标识符执行到这里的时候就会立即执行。
Promise透过构造表达式同步执行,执行结果调用promise的内部表达式存储,通常叫resolve和reject,四个是获得成功时存储存储使用的地下通道,另四个是失利时存储的地下通道,不论存储到哪个地下通道中,都是写标识符的人去定义的
then有四个参数,分别是四个表达式类型的参数,第四个参数是在调用resolve时会触发,第二个参数是在调用reject时触发
catch方式能替代then的第二个参数,拿到reject结果
附赠then第二个参数和catch的区别
在then的第四个参数里面的标识符,如果出现异常的时候,不用手动的try…catch,透过promise示例第一类的.catch能捕获then内出现的异常,但注意,catch不会捕获构造表达式代码中的错误,来看例子
new Promise(function (ok, fail) {
setTimeout(() => {
//故意5秒后触发k的报错
console.log(k)
}, 5000)
}).then((res) => {
console.log(res)
}).catch(error => {
//这个时候,error是拿不到那个错误的,他不负责console.log(k)所在标识符块中出现的错误
console.log(error)
})
再看四个catch方式能捕获什么地方的错误
大概就是这么个大概使用 Promise 的主要原因是他能解决反弹地狱(反弹嵌套)问题,让标识符变得更优雅,逻辑更清晰。
举四个生活中的例子,早上起床第一件事是要穿拖鞋,第二件事是洗漱,第三件事是穿衣服,第四件事是查看“身手要钱”,第五件事是打开自家房门出去开车上班,每件事都需要串行,每一步与下一步都有关联关系
function foo() {
//1、穿拖鞋已经开始
setTimeout(() => {
//1、2秒后穿拖鞋完成
//2、洗漱已经开始
setTimeout(() => {
//2、2秒后洗漱完成
//3、穿衣服已经开始
setTimeout(()=>{
//3、穿衣服完成
//….不好意思看官,后边还有好两个步骤咱就意思一下,再写就吐了
},2)
}, 2)
}, 2)
}
foo()
就写这几层吧,是不是太恶心了
new Promise((resolve, reject) => {
//1、穿拖鞋
setTimeout(() => {
resolve(穿拖鞋搞定)
}, 2000)
}).then(val => {
//等候穿拖鞋完成后,会调用这个表达式
//2、洗漱
//注意此处!!!,必须使用return 返回四个新的promise来完成链式调用
const p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(洗漱搞定)
}, 2000)
})
return p
}).then(val => {
//3、穿衣服,此处直接返回,没有使用中间变量
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(穿衣服搞定)
}, 2000)
})
}).then(val => {
//4、查看“身手要钱”
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(查看“身手要钱”搞定)
}, 2000)
})
}).then(val => {
//5、开车去上班
// 元气满满的一天
})
Promise其他方式
那么多方式,不讲那么多,race、all什么的网上一抓一大把。说说语法糖await和async的用法
先介绍四个基础规则
await必须修饰的是Promise第一类 await必须在async中使用 await只能接收resolve地下通道的结果,reject结果会导致报错 await修饰的标识符在执行时,标识符是卡住的,类似于alert,这句标识符不执行完,后边的标识符不会向下执行,这也类似于线程同步执行的概念,这也是await有用之处 async修饰的必须是表达式 async修饰后的表达式在执行之后会转为Promise第一类看几段单纯的标识符
async function foo() {
let k = await new Promise(function (resolve, reject) {
setTimeout(() => {
resolve(qfedu)
}, 2000)
})
console.log(k)
}
foo()
这种用倒是更麻烦,我们把要处理的事黏黏糊糊多弄许多试一试
async function foo() {
let level1 = await new Promise((resolve, reject) => {
//1、穿拖鞋
setTimeout(() => {
resolve(穿拖鞋搞定)
}, 1000)
})
//拿着第一步的结果,去第二步进行操作
let level2 = await new Promise((resolve, reject) => {
setTimeout(() => {
resolve(level1 + 洗漱搞定)
}, 1000)
})
let level3 = await new Promise((resolve, reject) => {
setTimeout(() => {
resolve(level2 + 穿衣服搞定)
}, 1000)
})
let level4 = await new Promise((resolve, reject) => {
setTimeout(() => {
resolve(level3 + 查看“身手要钱”搞定)
}, 1000)
})
console.log(level4 + ,之后开车去上班,元气满满的一天)
}
foo()
输出结果:
这种标识符看上去更加简洁,当然要重点考虑的问题是在整个从上到下的调用过程中,任何四个环节出现问题,都会影响下面的标识符
再来,我们把标识符聚焦到foo()方式调用之前和调用之后
console.log(1)
foo() //这个会输出 穿拖鞋搞定洗漱搞定穿衣服搞定查看“身手要钱”搞定……等
console.log(2)
思考一下,程序输出的顺序
注意,使用async包裹的标识符,属于异步标识符,会在同步标识符之后执行
我们给按钮添加四个点击事件,看点击按钮如何让程序使用await顺序执行
async function foo() {
let level1 = await new Promise((resolve, reject) => {
//1、穿拖鞋
setTimeout(() => {
resolve(穿拖鞋搞定)
}, 1000)
})
//拿着第一步的结果,去第二步进行操作
let level2 = await new Promise((resolve, reject) => {
setTimeout(() => {
resolve(level1 + 洗漱搞定)
}, 1000)
})
let level3 = await new Promise((resolve, reject) => {
setTimeout(() => {
resolve(level2 + 穿衣服搞定)
}, 1000)
})
let level4 = await new Promise((resolve, reject) => {
setTimeout(() => {
resolve(level3 + 查看“身手要钱”搞定)
}, 1000)
})
console.log(level4 + ,之后开车去qfedu上班,元气满满的一天)
}
window.onclick = foo;
//或者是
window.onclick = async function(){
//todo …
//await new Promise…
//await new Promise…
//await new Promise…
//…
}
实际场景中,await 与async通常见来处理ajax请求类标识符,让标识符更简洁,再次强调,await只接收resolve结果,注意reject和error的错误要使用try…catch…处理异常。