JS中的异步与Promise使用

249 阅读11分钟

同步与异步

我们知道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介绍

PromiseJavaScript 异步编程的一种流行解决方案(号称是终极解决方案),掌握 Promise 的使用是js开发者不可或缺的一项基本技能。

ES6 Promise 对象

ES6的Promise对象是一个构造函数,用来生成Promise实例。 Promise 的构造函数必须接收一个函数参数(也就是需要执行异步任务的函数),该函数将在传入以后立即调用,并传入 Promise 对象下的两个方法 resolvereject 所谓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 : 已失败
  • 每一个 Promise 对象只能由 PENDING 状态变成 FULFILLEDREJECTED,且状态发生变化以后就能再改变了
  • 一个 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方法

thenPromise 对象提供的一个方法,它接收两个函数作为参数,分别注册到 resolvereject 方法执行后的任务队列中,后面我们自己实现一个JS版本的Pormise,需要维护这两个队列

  • fulfilledQueues
  • rejectedQueues

结果传递

resolvereject之后就会执行对应的注册任务队列,可以通过传入一些值,在后续的 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),那对应到具体的逻辑是

  1. 第一次在调用then方法时传递的两个函数val=>{ console.log('success1',val); return 'p1-val'err=>console.log('err1', err)分别注册到fulfilledQueuesrejectedQueues队列中
  2. 第二次调用then方法时传递的两个函数v=>console.log('sucess2',val)err=>console.log('err2', err)再次push注册到fulfilledQueuesrejectedQueues队列中
  3. 第三次调用then方法时传递的两个函数val=>{ console.log('success3',val); return Promise.resolve('promise中的值')err=>console.log('err2', err)再次注册到fulfilledQueuesrejectedQueues队列中
  4. 1s中之后触发了resolve,会取到p实例中的fulfilledQueues函数数组并把val作为参数传递给队列中的函数依次执行(如果是调用了reject就会调用p示例中的rejectedQueues队列执行其中的注册函数)对应的效果就是打印出sucess1 valsucess2 valsuccess3 val
  5. 后面执行p1, p2, p3第二次调用的方法,我们可以看到第一次p1中调then方法返回了一个非promise的普通值,那就会把这个普通值包装成Promise返回给下一个then,所以下一个then接受到的就是'p1-val'
  6. 第一次p2中调用的then方法没有返回值,也会封装成一个undefined的Promise对象。所以p2的第二个then函数接受到的值就是undefine
  7. 第一次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