进程和线程
- 进程(厂房):程序的运行环境
- 线程(工人):实际进行运算的东西
同步
- 通常情况下代码都是自上向下一行一行执行的
- 前面的代码若没执行,后面的也不会执行
- 同步的代码执行会出现阻塞的情况
解决同步问题
- java python
- 通过多线程来解决(不同的线程干不同的活)
- node.js
- 通过异步的方式来解决(执行速度慢的代码一般不影响别的代码,在等待的过程中同时执行别的代码)
- java python
异步
- 一段代码的执行不会影响到其他的程序
- 异步的问题 :
- 异步的代码无法通过return来设置返回值
- 特点:
- 不会阻塞其他代码的执行
- 需要通过回调函数来返回结果
- 理解回调函数
点击查看代码
//传入三个参数,其中第三个是一个回调函数
//将 a + b作为参数传入回调函数
function sum(a b, callback) {
setTimeout(() => {
callback(a + b)
}, 1000)
}
//这里result是回调函数接收到的参数,也就是a + b
sum(1, 2, function(result) {
console.log(result)
})
- 基于回调函数的异步带来的问题
- 代码的可读性差
- 可调式性差
function sum(a, b, cb){
setTimeout(()=>{
cb(a + b)
}, 10000)
}
sum(123, 456, result => {
sum(result, 777, result => {
console.log(result)
})
})
上例中,调用了两次sum,第一次调用计算了123和456的和,第二次调用是求第一次的运算结果和456的和。由于异步函数的结果只能在回调函数中访问,所以我们只能在回调函数中第二次调用sum。当然,如果事情仅仅是这样其实还好。
现在假设我需要连续调用4个异步函数,且后一个要依赖于前一个的运算结果,也许会是这样的:
function sum(a, b, cb){
setTimeout(()=>{
cb(a + b)
}, 10000)
}
sum(123, 456, result => {
sum(result, 777, result => {
sum(result, 888, result => {
sum(result, 999, result => {
console.log(result)
})
})
})
})
这样一来我们就见识到了传说中的“回调地狱”,又名“死亡金字塔”。这还只是4次,现实的代码可能比这个还要再复杂一些。比如,某个功能需要用到三个不同异步函数返回的结果。
总之,异步提高了代码运行的效率,同样也增加了代码的复杂程度。于是,我们便需要一个新的东西让我们可以更加优雅的编写异步的代码,Promise应运而生。
- 解决问题:
- 需要一个东西,可以代替回调函数带给我们返回结果
- Promise出现了!
- Promise是一个可以用来存储数据的对象
- Promise存储数据的方式比较特殊,这种特殊方式使得Promise可以用来存储异步调用的数据
- Promise是一个可以用来存储数据的对象
Promise
只要是通过回调函数来获取异步的结果,就一定会遇到回调地狱之类的问题。为了解决这个问题,JS为我们提供一个对象 —— Promise,Promise意为承诺,它可以用来存储一个值,并确保在你需要将这个值返回。
创建Promise
Promise存储值的方式非常特别,先来看看它的构造函数:
const promise = new Promise(executor)
创建Promise时需要一个executor(执行器)为参数,执行器是一个回调函数,进一步调用大概长这个样子
const promise = new Promise((resolve, reject) => {
})
回调函数在执行时会收到两个参数,两个参数都是函数。第一个函数通常命名为resolve,第二个函数通常会命名为reject。向Promise中存储值的关键就在于这两个函数,可以将想要存储到Promise中的值作为函数的参数传递,像是这样:
const promise = new Promise((resolve, reject) => {
console.log('哈哈')
})
这样我们就将”哈哈”这个字符串存储到了Promise中,那么问题又来了,为什么需要两个函数存储值呢?很简单,resolve
用来存储运行正确时的数据,reject
用来存储运行出错时的错误信息。我们在使用Promise时需要根据不同的情况,调用不同的函数来存储不同的数据。
Promise是专门为了异步调用而生的,所以Promise中存储的主要是异步调用的数据,也就是那些本来需要通过回调函数来传递的数据。在Promise中,可以直接调用异步代码,在异步代码执行完毕后直接调用resolve或reject来将执行结果存储到Promise中,这就解决了异步代码无法设置返回值的问题。换句话说,异步代码的执行结果可以直接存储到Promise中,像是这样:
//通过setTimeout实现了一个异步调用,定时器会在10秒后执行,并调用resolve将”哈哈”存储到Promise中。
const promise = new Promise((resolve, reject) = > {
setTimeout(() => {
resolve('哈哈')
}, 1000)
})
获取Promise中的数据
then
then是Promise的实例方法,通过该方法可以获取到Promise中存储的数据。它需要一个回调函数作为参数,Promise中存储的数据会作为回调函数的实参返回给我们:
const promise = new Promise((resolve, reject) = > {
setTimeout(() => {
resolve('哈哈')
}, 1000)
})
promise.then((data) => {
console.log(data)//打印出来是 哈哈
})
这种方式只适合读取通过resolve存储的数据,如果存储数据时出现了错误,或者是通过reject存储的数据,这种方式是读取不到的:
const promise = new Promise((resolve, reject) => {
throw new Error("出错了!")
setTimeout(() => {
resolve("哈哈")
}, 10000)
})
promise.then((data) => {
console.log(data)
})
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
reject("哈哈")
}, 10000)
})
promise.then((data) => {
console.log(data)
})
上边的两块代码都是读取不到数据的,而且运行时会在控制台报出错误信息。这是因为,then的第一个参数只负责读取Promise中代码正常执行的结果,也就是只有Promise中数据正常时才会被调用。当Promise中的代码出错,或通过reject来添加数据时,我们还需要为其指定第二个参数来处理错误。
then的第二个参数也是回调函数,与第一个结构相同,不同点在于第二个函数只在出现错误/通过reject存储数据时调用:
const promise = new Promise((resolve, reject) => {
throw new Error("主动抛出错误")
setTimeout(() => {
resolve("哈哈")
}, 10000)
})
promise.then((data) => {
console.log(data)
}, (err) => {
console.log("出错了", err)
})
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
reject("哈哈")
}, 10000)
})
promise.then((data) => {
console.log(data)
}, (err) => {
console.log("出错了", err)
})
上边两个案例中,then的第二个回调函数会执行。执行时异常信息或时reject中返回的数据会作为参数传递。现实开发中,第二个回调函数通常会用来编写异常处理的代码。
catch
除了then以外,Promise中还有一个catch方法,catch和then使用方式类似,但是catch中只需要一个回调函数作为参数。
catch中回调函数的作用等同于then中的第二个回调函数,会在执行出错时被调用。既然有了then的第二个参数,为什么还需要一个catch呢?两个回调函数都写到then中,会导致代码不够清晰,但是多了一个catch后立刻就变的不一样了,开发时通常会在then中编写正常运行时的代码,catch中编写出现异常后要执行的代码:
const promise = new Promise((resolve, reject) => {
reject("出错了")
})
// 出现异常,then中只传了一个回调函数,无法读取数据
// promise.then((data) => {
// console.log(data)
// })
// 出现异常,可以通过catch来读取数据
promise.catch(err => {
console.log(err)
})
当Promise中代码执行出错时(或者reject执行时),如果我们调用的是catch来处理数据,则Promise会将错误信息传递给catch的回调函数,我们便可以在catch中处理异常,同时catch回调函数的返回值会作为下一步Promise中的数据向下传递。如果我们调用了then来处理数据,同时没有传递第二个参数,这时then是不会执行的,而是将错误信息直接添加到下一步返回的Promise中,由后续的方法处理。在后续调用中如果有catch或then的第二个参数,则正常处理。如果没有,则报错。
简言之,处理Promise时,如果没有对Promise中的异常进行处理(无论是then的二参数,还是catch),则异常信息总是会封装到下一步的Promise中进行传递,直到找到异常处理的代码位置,如果一直没有处理,则报错。
这种设计方式使得我们可以在任意的位置对Promise的异常进行处理,例如有如下代码:
function sum(a, b) {
return new Promise((resolve, reject) => {
if (Math.random() > 0.7) {
throw new Error("出错了")
}
resolve(a + b)
})
}
sum(123, 456)
.then(result => sum(result, 777))
.then(result => sum(result, 888))
.then(result => console.log(result))
上例代码中,sum函数有一定的几率会出现异常,但是我们并不确定何时会出现异常,这时有了catch就变的非常的方便,因为在出现异常后所有的then在异常处理前都不会执行,所以我们可以将catch写在调用链的最后,这样无论哪一步出现异常,我们都可以在最后统一处理。像是这样:
sum(123, 456)
.then(result => sum(result, 777))
.then(result => sum(result, 888))
.then(result => console.log(result))
.catch(err => console.log("哎呀出错了,随便返回一个吧", 8888))
当然如果我们想在中间处理异常也是没有问题的,只是需要注意在链式调用中间处理异常时,由于后续还有then要执行,所以一定不要忘了考虑是否需要在catch中返回一个结果供后续的Promise使用:
sum(123, 456)
.then(result => sum(result, 777))
.catch(err => {
// 也可以在调用链的中间处理异常
console.log("出错了,我选择忽略这个错误,重新计算")
return sum(123, 456)
})
.then(result => sum(result, 888))
.then(result => console.log(result))
.catch(err => console.log("哎呀出错了,随便返回一个吧", 8888))
还有一点要强调一下,在Promise正常执行的情况下如果遇到catch,catch是不会执行的,此时Promise中的结果会自动传递给下一个Promise供后续使用。
finally
finally也是Promise的实例方法之一,和then、catch不同,无论何种情况finally中的回调函数总会执行,通常我们在finally中定义一些无论Promise正确执行与否都需要处理的工作。注意,finally的回调函数不会接收任何参数,同时finally的返回值也不会成为下一步的Promise中的结果。简单说,finally只是编写一些必须要执行的代码,不会对Promise产生任何实质的影响。
原理
在Promise中维护着两个隐藏的值:
PromiseResult
:真正存储值的地方,在Promise中无论是通过resolve、reject还是报错时的异常信息都会存储在PromiseResult中。PromiseState
:PromiseState有三种状态- pending:Promise的初始化状态,此时Promise中没有任何值。
- fulfilled:Promise的完成状态,此时表示值已经正常存储到了Promise中(通过resolve)。
- rejected:表示拒绝,此时表示值时通过reject存储的或是执行时出现了错误。
当我们调用Promise的then方法时,相当于为Promise设置了一个回调函数,换句话说,then中的回调函数不会立即执行,而是在Promise的PromiseState发生变化时才会执行。
如果PromiseState从pending变成了fulfilled,则then执行第一个回调函数,且PromiseResult的值作为参数传递给回调函数。
如果PromiseState从pending变成了rejected,则then执行第二个回调函数,且PromiseSResult的值作为参数传递给回调函数。
then执行后每次总会返回一个新的Promise,并将then中回调函数的返回值存储到这个Promise中,如果没有指定返回值则新Promise中不会存储任何值。
const promise = new Promise((resolve, reject) => {
resolve('第一步执行结果')
})
const promise2 = promise.then(result => {
console.log('收到结果', result)
return '第二部执行结果'// 会作为新的结果存储到新Promise中
})
const promise3 = promise2.then(result => {
console.log('收到结果', result)
return '第三步执行结果'// 会作为新的结果存储到新Promise中
})
简写:
const promise = new Promise((resolve, reject) => {
resolve('第一步')
})
promise.then(result => {
console.log('收到', result)
return '第二步'// 会作为新的结果存储到新Promise中
}).then(result => {
console.log('收到', result)
return '第三步'// 会作为新的结果存储到新Promise中
})
第一个then用来读取上边我们创建的Promise中存储的结果,第二个then用来读取第一个then所返回的结果,依此类推我们就可以根据需要一直then下去,如此便解决了“回调地狱”的问题。
有了Promise后,在异步函数中我们便不再需要通过回调函数来返回结果,取而代之的是返回一个Promise,并将异步执行的结果存储到Promise中,像是这样
function sum(a, b){
return new Promise((resolve, reject) => {
setTimeout(()=>{
resolve(a + b)
}, 10000)
})
}
由于sum的返回值是一个Promise,所以我们不在需要通过回调函数来读取结果:
sum(123, 456).then(result => {
console.log("结果为:", result) // 结果为: 579
})
如果需要连续多次调用,也不会在有“回调地狱的问题”:
sum(123, 456)
.then(result => sum(result, 777))
.then(result => sum(result, 888))
.then(result => console.log(result))
标签:异步,resolve,函数,sum,Promise,result,&&,reject
From: https://www.cnblogs.com/cloud0-0/p/17189747.html