likes
comments
collection
share

拥抱Promise,迈向异步编程的新篇章

作者站长头像
站长
· 阅读数 58

引言

Promise 是 JavaScript 中用于处理异步操作的一种编程模式,它为异步代码提供了一种更优雅、更易于理解和维护的解决方案。相比于传统的回调函数,Promise 能够以链式调用的方式组织代码,避免了回调地狱,让js执行效率更高。

为什么要用promise?

function a() {
  setTimeout(function () {
    console.log('敲代码');
  }, 1000)
}
a()

function b() {
  console.log('打游戏')
}
b() // 先执行

在上面这段代码中,先调用的是函数a,那为什么是函数b先执行呢,难道不是一秒后先执行a紧接着执行b吗? 这是因为在JavaScript中,代码是按照顺序执行的,但是当遇到异步操作时,就不一样了,上面的代码中就涉及到了一个典型的异步操作:setTimeout,为了执行效率更高,js引擎不会等待这个延时函数完成,而是继续执行后面的代码。

如果想要在一秒后先执行a紧接着执行b,我们就要这么写

function a(cb) {
  setTimeout(() => {
    console.log('敲代码')
    cb()
  }, 1000);
}

function b() {
  console.log('打游戏')
}
a(b)

这样一来,b函数的执行就会依赖a的执行结果,这种调用函数的方式我们称之为回调,如果函数数量多起来的话,过多的回调函数会使代码难以阅读和维护,就会形成回调地狱,因此引入了一个新的用于处理异步操作的一种编程模式---Promise

什么是Promise?

Promise 是一个构造函数(也可以看成是一个对象),代表一个异步操作的结果,这个结果可能已经完成、正在进行中或尚未开始。它有三种状态:

  1. pending(等待中):初始状态,既没有被兑现,也没有被拒绝
  2. fulfilled(已成功):意味着操作成功完成
  3. rejected(已失败):意味着操作失败

一旦 Promise 的状态从 pending 变为 fulfilled 或 rejected,就不会再改变

promise的基本用法(all,race)

来看一段代码

function Blind date() {
  return new Promise((resolve, reject) => {
  // 回调函数的参数就是一个函数体
    setTimeout(() => {
      console.log('相亲')
      resolve('成功')  //通常这里放的是请求到的数据
    }, 2000)
  })
}

function marry() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('结婚')
      resolve('顺利')
    }, 1000)
  })
}

function baby() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('孩子')
      resolve()
    }, 500)
  })
}

function baby2() {
  setTimeout(() => {
    console.log('二胎')
  }, 300)
}


Blind date()
  .then((res) => {
    console.log(res)
    return marry() // 覆盖掉默认自带的promise
  })
  .then((res2) => {
    console.log(res2)
    return baby()
  })
  .then(() => {
    baby2()
  })

代码有点长,我们来梳理一下

首先我们创建了四个函数,分别是Blind date()marry()baby()baby2(),如何才能让他们按顺序顺利执行呢,我们从第一个函数看起:

我们通常使用 new Promise() 来创建构造函数,Promise里面接收一个参数,这个参数是一个回调函数,然后我们在函数体内调用了resolve,因为写在Promise函数里的参数,本身就是一个函数,所以是可以直接调用的,在new了一个构造函数之后一定会得到一个实例对象,也就是说,在Blind date执行完之后一定会得到一个对象,然后看到下面的Blind date()后面接了.then方法,它里面也接收一个实参为回调函数,可以在实参里写你想实现的效果,这里的形参res的参数是用来接收前面resolve出来的东西,这样就能在控制台打印出相亲成功了。

接下来如何让他结婚呢,这个时候就可以直接在.then里返回marry函数,这里的return是一定一定要写的,否则baby就不会等marry执行完再执行(baby的执行时间更短),只要写了return,第一个.then里返回的实例对象Promise不执行完是无法继续执行到第二个.then的,原因在于实例对象能够访问.then显然.then是写在了Promise构造函数的原型上,它身上有一个then方法,才可以在我们new了一个Promise得到的实例对象后面可以接一个.then。如果不写返回值,它会自动有一个默认值,会创建一个新的Promise出来,显然不是我们想要的marry()函数,只要写了返回值,就会覆盖掉默认自带的Promise,这样一来就可以看懂这段代码了。

在这个例子中我们已经涉及到了一个新的概念---

then的链式调用

通过.then方法,我们可以将一系列基于前一个Promise结果的操作链接起来,将多个操作依次执行,在这个例子中,每个.then都是在前一个Promise成功(resolve)后执行的,而.catch则用来捕获整个链中可能出现的错误,也可以说.catch是为rejected兜底的,在返回的是错误值的时候不会影响代码继续向下执行,来看下一个例子就可以清晰地明白这一点:

function a() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('a')
      resolve('yes') // 代表执行顺利反之失败
    }, 1000)
  })
}
function b() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('b')
      reject('no')
    }, 1500)
  })
}

//resolve用then
a()
.then((res) => {
  console.log(res)
})
//reject用catch
b()
.catch((err) => {
  console.log(err)
})

接着上面的例子再来介绍两个常用的方法,在函数ab后面新建一个函数c

function c() {
  console.log('c')
}

如何让函数c在函数ab执行完之后执行呢?

有一个all方法可以实现这个效果:

Promise.all([a(), b()]).then(c)

但是!这样还不够严谨,all必须接收到的所有Promise状态都为resolvethen才会调用。

其实,实现这个效果以跑的最慢的函数为准执行回调的,那么如何以跑的快的函数为基准来执行回调呢?

这里也有一个方法race来实现:

// 在函数a内调用reject('yes')
Promise
.race([a(), b()])
.then(c)
.catch((err) => {
    console.log(err) //a yes b
  })

race方法中,并不需要所有的Promise都必须是非rejected状态,这意味着,即使所有其他Promise都还在pending状态,或者最终会变为fulfilled状态,只要其中一个Promise变为rejectedPromise.race就会立即结束,返回一个rejectedPromise,在这个例子中,由于a()设置的延迟时间较短(1000ms),它比b()(1500ms)先完成,并且执行了reject('yes')。因此,Promise.race立即结束,并跳转到.catch((err) => { console.log(err); })这一部分,打印出yes,虽然Promise.racea()拒绝后就已经“决定”了结果,但这并不意味着它能直接阻止b()的执行。b()作为一个独立的异步操作,已经在事件队列中排队等待执行,因此它的setTimeout依然会执行,打印出b,总结来说,Promise.race确实是在a()b()中任意一个Promise完成时就“结束比赛”,但这里的“结束”是指它立即根据已完成的Promise的状态(fulfilledrejected)来决议自己,并调用相应的.then.catch处理程序,它并不能阻止其他Promise继续执行它们自己的逻辑,特别是那些已经在事件循环队列中排队的异步操作。

结语

嘿,朋友!经过这一路的探索,Promise是不是感觉没那么神秘,甚至还有点儿亲切了呢?就像是个总能在关键时刻帮你排忧解难的好伙伴,Promise在JavaScript的异步世界里,就是我们的好帮手。想象一下,以前写异步代码就像安排一场复杂的多米诺骨牌,一不小心,整个序列就乱套了。但现在有了Promise,就像是给每个骨牌装上了智能导航,它们自己知道什么时候该倒下,而且还能互相通知:“嘿,我搞定了,该你上场啦!”这样,咱们的代码就变得井井有条,看起来舒服多了。

不管是通过.then串起的一连串操作,还是用all集结小伙伴们一起行动,或是race里那个速度最快的先冲线,Promise总有办法让异步任务变得既高效又优雅。就连处理错误,也有.catch在那里默默守候,随时准备接手烂摊子,不让任何小差错影响到大局。最重要的是,Promise让我们学会了怎样在纷繁复杂的异步操作中找到那份从容和淡定。现在,就算面对再复杂的异步逻辑,也能像泡一杯茶一样,慢慢享受过程,不再担心会被回调地狱困住。

好了,今天的旅程就到这里,下次见!

转载自:https://juejin.cn/post/7376158566598197263
评论
请登录