吃透Promise

异步处理代码存在的困境

首先看下面一个示例代码,用户点击千层面按钮,程序等待两秒,然后告知用户取餐提醒

html 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <button class="burger">千层面</button>

  <script>
    var burgerEL = document.querySelector(".burger");
    // 异步任务
    burgerEL.onclick = function (){
      function excuteCode(callback) {
      console.log("请稍等一下")
      setTimeout(() => {
        var order = burgerEL.textContent;
        callback(order);
      }, 2000)
    }
    excuteCode((data) => {
      console.log(`你的${data}已经做好了`)
    })
    }
    
  </script>
</body>

</html>

下面是优化代码,设置了千层面的余量,如果余量不足,则告知用户卖光了。

html 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <button class="burger">千层面</button>

  <script>
    var balance = 2
    var burgerEL = document.querySelector(".burger");
    var order = burgerEL.textContent;
    // 异步任务
    burgerEL.onclick = function () {

      function excuteCode(successCallback, failCallback) {
        if (balance >= 1) {
          console.log("请稍等一下")
          setTimeout(() => {
            successCallback(order);
            balance--;
          }, 2000)
        } else {
          failCallback(order);
        }

      }
      excuteCode((data) => {
        console.log(`你的${data}已经做好了`)
      }, (err) => {
        console.log(`不好意思,${err}卖光了`)
      })

    }

  </script>
</body>

</html>

当然上面这个我自己编写的小程序只有successCallback, failCallback两个传入参数,但是实际开发中肯定不会这么少,而且往往编写异步程序和编写执行代码的人不是一个人。

那么这个时候参数摆放的位置就很重要了,因为这直接决定了编写执行代码的人传入callback的顺序,例如一般人可能习惯于excuteCode(100, ( ) => { }, ( ) => { })这种顺序,但是编写逻辑的人完全可以不按照这种顺序定义,可能让你先传入函数再传入变量,你就不舒服了。

这并不是说另一个人故意刁难,而是每个人的设计思路确实不一样,

困境在这个小例子中就可以窥见一斑,虽然人们存在一种共识或者规范,但是依然不能保证每个人编写的方式按照标准来,因此ES5之前的解决方案(上面的代码)不适用了,进而在ES6之后引出了promise。

Promise解决异步处理

既然矛盾出在传参的地方,那么干脆直接不需要传了,但是不传参怎么回调呢?

想要理解promise的处理方式,我先放一个伪代码:

html 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <button class="burger">千层面</button>

  <script>
    var balance = 2
    var burgerEL = document.querySelector(".burger");
    var order = burgerEL.textContent;
    // 异步任务
    burgerEL.onclick = function () {

      function excuteCode() {
        const caller = new Caller ()   // 取餐呼叫器

        if (balance >= 1) {
          console.log("请稍等一下")
          setTimeout(() => {
            
            caller.success(order)

            balance--;
          }, 2000)
        } else {
          caller.failure(order)
        }

          return caller;
      }
     
      caller.监听震动((data) => {
        console.log(`$(data)卖光了 `)
      })
      caller.监听闪烁((err) => {
        console.log(`$(err)卖光了 `)
      })
    }

  </script>
</body>

</html>

这个伪代码于原来的差异在于不需要进行传参回调,而是创建了一个取餐呼叫器对象,然后在异步程序执行完毕后返回,因此,我们只需要调用这个对象的方法(震动代表成功,可以取餐;闪烁代表失败,无法取餐),就可以调用。

这实际上就是promise的逻辑,下面将伪代码替换成promise的实现:

html 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <button class="burger">千层面</button>

  <script>
    var balance = 2
    var burgerEL = document.querySelector(".burger");
    var order = burgerEL.textContent;
    // 异步任务
    burgerEL.onclick = function () {

      function excuteCode() {
        const promise = new Promise((resolve, reject) => {
          // 一般将异步程序放在这个函数中,因为虽然放在外部的话也会被执行,但这样就无法调用resolve和reject
          if (balance >= 1) {
            console.log("请稍等一下")
            setTimeout(() => {

              resolve(order)

              balance--;
            }, 2000)
          } else {
            reject(order)
          }
        })
        return promise;
      }

      const promise = excuteCode()

      promise.then((data) => {
        console.log(`${data}做好了`)
      })
      promise.catch((err) => {
        console.log(`${err}卖光了`)
      })
    }

  </script>
</body>

</html>

将其中promise的结构抽象出来就是:

JavaScript 复制代码
const promise = new Promise((resolve, reject) => {
          resolve()
          reject()
        })
promise.then(() => { }).catch(() => { })

Promise是一个类,可以翻译成 承诺、许诺 、期约。

当我们需要的时候,给予调用者一个承诺: 待会儿我会给你回调数据时,就可以创建一个Promise的对象口在通过new创建Promise对象时,我们需要传入一个回调函数,我们称之为executor

这个回调函数会被立即执行,并且给传入另外两个回调函数resolve、 reject。

当我们调用resolve回调函数时会执行Promise对象的then方法传入的回调函数当我们调用reject回调函数时,会执行Promise对象的catch方法传入的回调函数。

至此就是promise最核心的内容,可能刚上手很多人觉得还不如之前的方法,但是熟悉之后你就会习惯这种规范,体会到他的优势。

promise的各个状态

 待定(pending) : 初始状态,既没有被兑现,也没有被拒绝;

✓ 当执行executor中的代码时,处于该状态;

 已兑现(fulfilled) : 意味着操作成功完成;

✓ 执行了resolve时,处于该状态, Promise已经被兑现;

 已拒绝(rejected) : 意味着操作失败;

✓ 执行了reject时,处于该状态, Promise已经被拒绝;

注意:promise的状态一旦被确定下来就不能再更改,也不能再执行下一个回调函数来改变状态。

关于then的返回值

先来看一个小例子:

JavaScript 复制代码
<script>
    const promise = new Promise((resolve, reject) => {
      resolve("aaaa")
    })
  	// 第一个promise对象
    promise.then(res => {
      console.log("第一个then", res)
    }).then(res => {
      console.log("第二个then", res)
    })
		// 第二个promise对象
    promise.then(res => {
      console.log("独立的then", res)
    })

  </script>

前面我们已经说过,then方法是可以进行链式调用的,那么上面这个小例子中总共有三个then方法,究竟哪些then方法会被成功执行呢?

看一下输出,三个then方法均被成功执行,但是细心的你一定发现输出的顺序好像有点奇怪,并且出现了一个undefined。

这是因为then方法执行后会返回一个promise对象,而第一个promise对象的第二个then方法等待的是上面一个then方法放回的promise对象的决议,而不是原先的promise的决议,以此类推。 这句话可能听起来有点绕,但这就是关键所在,好好理解一下。如果没有返回值,默认返回undefined。

因此当我们给第一个then方法编写一个特定的返回值,promise源码就会创建一个新的promise对象,并在对象内部用resolve包含这个返回值,进而后面一个then方法等待这个resolve方法的决议。

我们根据这个逻辑修改代码,就不会出现undefined错误了。

JavaScript 复制代码
<script>
    const promise = new Promise((resolve, reject) => {
      resolve("aaaa")
    })
  	// 第一个promise对象
    promise.then(res => {
      console.log("第一个then", res)
      return "bbbbb"
    }).then(res => {
      console.log("第二个then", res)
    })
		// 第二个promise对象
    promise.then(res => {
      console.log("独立的then", res)
    })

  </script>
相关推荐
yzzzzzzzzzzzzzzzzz5 小时前
JavaScript 操作 DOM
开发语言·javascript·ecmascript
奋斗的小羊羊5 小时前
HTML5关键知识点之多种视频编码工具的使用方法
前端·音视频·html5
前端呆猿5 小时前
深入解析HTML5中的object-fit属性
前端·css·html5
再学一点就睡5 小时前
实现大文件上传全流程详解(补偿版本)
前端·javascript·面试
你的人类朋友7 小时前
【Node&Vue】什么是ECMAScript?
前端·javascript·后端
路灯下的光7 小时前
用scss设计一下系统主题有什么方案吗
前端·css·scss
l_tian_tian_7 小时前
SpringClound——网关、服务保护和分布式事务
linux·服务器·前端
一只小风华~8 小时前
CSS @media 媒体查询
前端·css·媒体
shix .8 小时前
最近 | 黄淮教务 | 小工具合集
前端·javascript
John_ToDebug8 小时前
Chrome 内置扩展 vs WebUI:浏览器内核开发中的选择与实践
前端·c++·chrome