在了解promise之前,我们需要简单了解一些关于异步的知识,方便解释promise具体做了什么,起到什么样的作用
一、异步
-
js在设计之初时被作为浏览器脚本语言来设计,所以被设计成了单线程以节省用户的性能
-
js代码中如果存在需要耗时执行的代码(如
setTimeout()),则该代码在v8引擎执行到这时,会被暂时挂起,而优先执行后面不耗时的代码(同步代码),这就是js中异步的简单理解
3.通过一段代码直观了解异步
javascript
let a = 1
let b = 2
setTimeout(() => {
console.log(a)
},1000)
console.log(b)
输出结果为2,一秒后再输出1
所以,如果你不了解什么是异步,那么你是否会觉得是函数在读取到第四行代码后,等待一秒后打印1再打印2?
二、如何处理异步
通过上个标题内容,你是否了解到了在v8中引擎执行的顺序? 那么,js中异步这一种情况会给我们带来什么样的问题与困境呢,不急,请看如下情形中的代码
scss
let a = 1
function fn(){
setTimeout(()=>{
a = 2
console.log(a)
},1000)
}
function foo(){
setTimeout(()=>{
a = 3
console.log(a)
},2000)
}
function bar(){
console.log(a)
}
foo()
fn()
bar()
此时由于异步的存在,你会发现,无论你如何调用foo(),fn(),bar(),都是以bar(),fn(),foo()这样的顺序依次打印出来,使得你无法控制他们的执行顺序,那么此时我们该如何解决这个问题呢
1、解决方法
(1)通过函数嵌套回调调用,改变执行顺序(容易形成屎山代码,不推荐)
此时你想到一个办法,我通过将下一个执行的函数调用于上一个执行函数的末尾,不就能解决异步的问题了吗,通过这个手段就可以实现执行顺序为bar(),fn(),bar()了,于是你写出了这样的代码
scss
let a = 1
function fn(){
setTimeout(()=>{
a = 2
console.log(a)
},1000)
bar()
}
function foo(){
setTimeout(()=>{
a = 3
console.log(a)
fn()
},2000)
}
function bar(){
console.log(a)
}
foo()
缺陷
此时你便成功实现了执行顺序为bar(),fn(),bar(),但是,这又引发了另一个问题,仔细思考,这里是三个函数的回调,
但是你要是有大量的函数需要回调呢?此时你会发现虽然代码依然可以运行,且按照了你预想的顺序执行,但是,一旦出现了bug,你便会发现一件事,那就是你只能在该回调函数中一个一个寻找错误点,使得项目代码难以阅读和维护 ,让人十分难以解决该bug,这就是屎山代码的样子
大量函数代码的回调形成的维护困难这一情况,也称之为回调地域,我们在平时解决异步问题时,要避免造成这种情况
(2)通过使用Promise的链式调用,解决异步问题
虽然函数嵌套回调调用可以解决问题,但是带来的代码难以阅读与维护,寻找错误困难的问题 也不是我们希望能看见的,所以,我们此时应用Promise这一函数来解决异步的问题
Promise的救赎
当你第一次见到Promise,你或许会想,这不是承诺的意思吗?什么是承诺?
我们继续通过代码来使得更好的理解
javascript
function gohome(){
setTimeout(() => {
console.log('成功回家')
},4000)
}
function shower(){
setTimeout(() => {
console.log('你洗澡了')
},1000)
}
function gosleep(){
setTimeout(() => {
console.log('你上床休息了')
},2000)
}
此时由于三个函数均要消耗时间,且根据所需消耗时间来判断,无论什么顺序调用函数,都是以shower,gosleep,gohome的顺序执行的,此时我们便需要使用Promise函数来解决这个问题了
表达式一
scss
function gohome() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('成功回家')
resolve()
}, 4000)
})
}
function shower() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('你洗澡了')
resolve()
}, 1000)
})
}
function gosleep() {
setTimeout(() => {
console.log('你上床休息了')
}, 2000)
}
gohome().then(() => {
shower().then(() => {
gosleep()
})
})
运行以上代码,你就能发现,问题解决,使得函数构成了链式的调用,且使得代码更扁平,更易管理
除了上面代码这种方法,promise还有另一种使用方式,能使代码更简洁明了,更加直观
表达式二
javascript
function gohome() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('成功回家')
resolve()
}, 4000)
})
}
function shower() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('你洗澡了')
resolve()
}, 1000)
})
}
function gosleep() {
setTimeout(() => {
console.log('你上床休息了')
}, 2000)
}
gohome().then(() => {
return shower()
})
.then(() =>{
gosleep()
})
详细说明
-
使用了Promise函数后,原函数获得一个实例对象,只有由Promise构造出的实例对象后面才能接then()
-
Promise函数要传入一个函数,而传入的这个函数还需要接受两个参数(resolve,reject),分别需要再Promise函数中调用,用于表示成功与失败若读取到成功,则立即执行其实例对象所接到的then()内部的函数,若失败则不执行
-
then()只能被由Promise函数创建的实例对象调用,原因是then()存放在Promise()的显示原型上,而其 实例对象的隐式原型继承了其构造函数的显示原型,then()内部要传入一个函数,当其得到Promise()中resolve()被执行的信息后,会立即执行then()内部传入的函数 (若对原型有疑问,参考前文深入浅出:理解js的'万物皆对象'与原型链)
4.在表达式二中,该表达方法中的第一个then()会默认继承上Promise内读取到的resolve或reject
- 在.then中返回的值(无论是普通值还是新的Promise),都会成为下一个.then接受的参数,所以为了在使用表达式二中,避免后续的then全部默认接受同一个参数,记得为每一个then添加一个返回值,避免出现''漂浮的Promise''