同步与异步
我们知道JS是一个单线程的语言,即在同一时间只能做一件事情。为什么设计为当线程呢。?在早期JS是为了在浏览器中运行,我们可以利用JS来制作一些页面的效果也可以和用户做一些交互。所以设计为单线程也是为了避免复杂度。比如在网页上有若干个操作,也就是在主线程中有多个任务,一个线程任务是在某个DOM节点上添加内容,另一个线程任务是删除这个节点,这时浏览器应该以哪个线程为准呢。?这就复杂了
基本概念
上面说过由于JS被设计成为一种单线程语言,即同一时间只能做一件事,这件事做完了才会做后面的事,如果当前处理的任务比较长,那后面的任务就一直没有机会被执行了。所以就出现了异步的机制。相对异步,也就有了同步的概念。其实同步和异步是一种消息通知机制在JS中有几个概念区分一下:
-
同步任务:指的是在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;
-
异步任务:指的是不进入主线程、而进入"任务队列"(task queue)的任务,只有等主线程任务执行完毕,"任务队列"开始通知主线程,请求执行任务,该任务才会进入主线程执行
-
阻塞:简单来说就是,A调用B,A在等待B返回结果之前,如果当前的线程是处于挂起状态,那么就叫阻塞。
-
非阻塞:与阻塞相反,如果A调用B,当前线程仍处于运行状态,就意味当前线程是可以的继续处理其他任务,但要时不时的去看下是否有结果了,这就是非阻塞。而等B执行完又结果之后可以通过回调的方式再处理
所以这里需要注意判断阻塞还是非阻塞和是不是同步/异步没有关系,主要看当前的线程处于挂起状态。并不是说同步一定是阻塞,而异步一定是非阻塞的。在其他有的编程语言中也有同步非阻塞和异步阻塞的。而在浏览器和Nodejs中是通过事件循环机制来实现异步非阻塞的。
下图中表示了在浏览器中的事件循环机制,主线程代码运行的时候,产生堆(heap)和栈(stack),栈中的代码调用各种外部API,它们在"任务队列"中加入各种事件(click,load,done)。只要栈中的代码执行完毕,主线程就会去读取"任务队列",依次执行那些事件所对应的回调函数。
异步处理方案
我们知道在早期ES6之前,使用的是回调的方式解决异步,缺点是很容易造成回调地狱。而在ES6之后就有了Promise。下面就重点介绍一下Promise
Promise介绍
Promise
是 JavaScript
异步编程的一种流行解决方案(号称是终极解决方案),掌握 Promise
的使用是js开发者不可或缺的一项基本技能。
ES6 Promise 对象
ES6的Promise对象是一个构造函数,用来生成Promise实例。
Promise
的构造函数必须接收一个函数参数(也就是需要执行异步任务的函数),该函数将在传入以后立即调用,并传入 Promise
对象下的两个方法 resolve
和 reject
所谓Promise对象,就是代表了未来某个将要发生的事件(通常是一个异步操作)。
它的好处在于,有了Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数
-
then 方法
-
then方法的2个参数;onresolove 和 onreject;
-
then的返回值,会返回一个新的 Promise 对象, 但是状态会有几种情况:
- then 的回调函数中没有返回值,then就会返回一个状态为: resolved 的 promise 对象
- then 的回调函数返回值是 非 promise 的值, then就会返回一个状态为: resolved 的 promise 对象,另外会把返回值,传递给 下一个 then
- then 的回调函数返回值是 promise 对象,then 就直接返回这个 promise 对象,具体的状态可以由我们自己定义,具体传递的值,也由我们自己定义
-
Promise 中的方法包括:resolve、reject、all、race、finally…
Promise 状态
- 每一个Promise对象都有三种状态: pending 、resolve 和 reject
- PENDING : 进行中,
Promise
对象的初始状态 - FULFILLED : 已成功
- REJECTED : 已失败
- PENDING : 进行中,
- 每一个
Promise
对象只能由PENDING
状态变成FULFILLED
或REJECTED
,且状态发生变化以后就能再改变了 - 一个
Promise
对象状态的变化并不由Promise
对象本身来决定,而应该是由我们传入的异步任务完成情况来决定的,Promise
提供了resolve, reject两个用来改变状态的方法
Promise中的静态方法
Promise.resolve 方法
Promise.resolve() 静态方法将给定的值转换为一个 Promise。如果该值本身就是一个 Promise,那么该 Promise 将被返回;
将 Promise
对象的状态从 PENDING
变为 FULFILLED
,并执行成功后的注册任务
比如下面的代码
注意:如果当前状态已经改变过了,则直接
return
Promise.reject 方法
Promise.reject() 静态方法返回一个已拒绝(rejected)的 Promise 对象,拒绝原因为给定的参数。
将 Promise
对象的状态从 PENDING
变为 REJECTED
,并执行失败后的注册任务
注意:如果当前状态已经改变过了,则直接
return
Promise.all方法
Promise.all() 静态方法接受一个 Promise 可迭代对象作为输入,并返回一个 Promise。当所有输入的 Promise 都被兑现时,返回的 Promise 也将被兑现(即使传入的是一个空的可迭代对象),并返回一个包含所有兑现值的数组。如果输入的任何 Promise 被拒绝,则返回的 Promise 将被拒绝,并带有第一个被拒绝的原因
Promise.allSettled方法
Promise.allSettled() 静态方法将一个 Promise 可迭代对象作为输入,并返回一个单独的 Promise。当所有输入的 Promise 都已敲定时(包括传入空的可迭代对象时),返回的 Promise 将被兑现,并带有描述每个 Promise 结果的对象数组。
这个方法也是为了弥补Promise.all方法中如果有某个promise被拒绝了,就没办法获取所有值的一个缺点
Promise.race方法
Promise.race() 静态方法接受一个 promise 可迭代对象作为输入,并返回一个 Promise。这个返回的 promise 会随着第一个 promise 的敲定而敲定。
Promise.any方法
Promise.any() 静态方法将一个 Promise 可迭代对象作为输入,并返回一个 Promise。当输入的任何一个 Promise 兑现时,这个返回的 Promise 将会兑现,并返回第一个兑现的值。当所有输入 Promise 都被拒绝(包括传递了空的可迭代对象)时,它会以一个包含拒绝原因数组的 AggregateError 拒绝。
Promise实例的方法
promise.then方法
then
是 Promise
对象提供的一个方法,它接收两个函数作为参数,分别注册到 resolve
和 reject
方法执行后的任务队列中,后面我们自己实现一个JS版本的Pormise,需要维护这两个队列
- fulfilledQueues
- rejectedQueues
结果传递
在resolve
和reject
之后就会执行对应的注册任务队列,可以通过传入一些值,在后续的 then
方法中,可以通过对应的函数接收到该结果
注意:Promise.then方法是一个微任务,Promise.catch也是微任务而Promise中的方法是同步任务
返回值
then
方法在执行最后必须返回一个新的 Promise
对象,其返回值像上面说的一样,一般有三种情况
- then 的回调函数中没有返回值,then就会返回一个状态为: resolved 的 promise 对象
- then 的回调函数返回值是 非 promise 的值, then就会返回一个状态为: resolved 的 promise 对象,另外会把返回值,传递给 下一个 then
- then 的回调函数返回值是 promise 对象,then 就直接返回这个 promise 对象,具体的状态可以由我们自己定义,具体传递的值,也由我们自己定义
例子说明
const p = new Promise((resolve, reject) => {
setTimeout(()=> resolve('val'), 1000)
})
// 注意,这里的p1,p2,p3是并行调用而非链式调用
// 第一次调用then
const p1 = p.then(val=>{
console.log('success1',val);
return 'p1-val'
}, err=>console.log('err1', err));
// 第二次调用then,then中没有返回值undefine
const p2 = p.then(val=>console.log('success2',val), err=>console.log('err2', err))
// 第三次调用then
const p3 = p.then(val=>{
console.log('success3',val);
// 返回一个promise对象
return Promise.resolve('promise中的值')
}, err=>console.log('err1', err));
p1.then(val => console.log('p1的第二个then函数', val))
p2.then(val => console.log('p2的第二个then函数', val))
p3.then(val => console.log('p3的第二个then函数', val))
比如上述代码,p是一个Promise的实例,这个实例p中先是调用了三次then方法(p1, p2, p3),那对应到具体的逻辑是
- 第一次在调用then方法时传递的两个函数
val=>{ console.log('success1',val); return 'p1-val'
、err=>console.log('err1', err)
分别注册到fulfilledQueues
和rejectedQueues
队列中 - 第二次调用then方法时传递的两个函数
v=>console.log('sucess2',val)
和err=>console.log('err2', err)
再次push注册到fulfilledQueues
和rejectedQueues
队列中 - 第三次调用then方法时传递的两个函数
val=>{ console.log('success3',val); return Promise.resolve('promise中的值')
和err=>console.log('err2', err)
再次注册到fulfilledQueues
和rejectedQueues
队列中 - 1s中之后触发了resolve,会取到p实例中的fulfilledQueues函数数组并把
val
作为参数传递给队列中的函数依次执行(如果是调用了reject就会调用p示例中的rejectedQueues队列执行其中的注册函数)对应的效果就是打印出sucess1 val
、sucess2 val
、success3 val
- 后面执行p1, p2, p3第二次调用的方法,我们可以看到第一次p1中调then方法返回了一个非promise的普通值,那就会把这个普通值包装成Promise返回给下一个then,所以下一个then接受到的就是’p1-val’
- 第一次p2中调用的then方法没有返回值,也会封装成一个undefined的Promise对象。所以p2的第二个then函数接受到的值就是
undefine
- 第一次p3中调用的then方法返回的是一个Promise对象,就会直接把这个Promise的结果(这里是resolve状态中的’promise中的值’)返回给下一个then
所以整体打印结果如下
需要注意的是,在Promise 构造函数中,返回的Promise是resolve中的值,而不是return的值、比如下面这一串代码
const p = new Promise((resolve, reject) => {
resolve(1)
console.log(3);
return 2
})
console.log(p);
p.then(val => console.log(val))
在Promise构造函数中,resolve的值是1,所以传递给下一个then的值就是1,或者是Promise构造函数的返回的promise实例的值是1,而不是return 2。我们习惯性的以为函数的返回值都是rerturn来的,但这里有点和我们的常识不一致。Promise构造函数值是看resolve中的值而非return
而在后续then中的返回值会成为返回promise的值,进而传递给下一个then
const p = new Promise((resolve, reject) => {
resolve(1)
return 2
})
console.log(p);
p.then(val=> {
console.log('接受来自Promise构造函数实例处Promise的值:', val)
return '返回的promise的值'
}).then(val => console.log(val))
promise.catch方法
Promise 对象的 catch() 方法用于注册一个在 promise 被拒绝时调用的函数。它会立即返回一个等价的 Promise 对象,这可以允许你链式调用其他 promise 的方法。此方法是 Promise.prototype.then(undefined, onRejected) 的一种简写形式。
比如下面代码
const promise = new Promise((resolve, reject) => {
setTimeout(() => reject('出错啦'), 1000)
})
promise.then(undefined, error => console.log('catch err in then:', error))
promise.catch(error => console.log('catch err:', error))
promise.finally方法
finally() 方法返回一个 Promise。在 promise 结束时,无论结果是 fulfilled 或者是 rejected,都会执行指定的回调函数。这为在 Promise 是否成功完成后都需要执行的代码提供了一种方式。这避免了同样的语句需要在 then() 和 catch() 中各写一次的情况。
const promise = new Promise((resolve, reject) => {
setTimeout(() => resolve('okk'), 3000)
})
promise
.then(val => console.log('success:', val), err => console.log('err', err))
.finally(() => {
// 不管promise成功还是失败,都会执行
// 由于无法知道状态,所以不接受任何参数,仅用于无论最终结果如何都要执行的情况
console.log('promise finally')
})
参考文档
Promise-catch
Promise-finally