2

这是你认识的Promise吗

 3 years ago
source link: https://segmentfault.com/a/1190000038463305
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
发布于 2020-12-13

本文字数、代码都比较多,建议先不看代码,有兴趣的时候再仔细研究。

被老学究迫害了十几年的我们本能的排斥严肃又充满教育意义的文章。所以总是不想把技术文章写的那么严肃。今天又是一个被无数人写过的课题。(此处略过十万字……)依然略过作者想说的一切废话,直入主题。

慢着✋, 让我们先来想一想,你希望能在这个文章中收获什么呢?

1.你知道promise是什么吗?

根据 百度翻译 介绍,promise主要有下面几种含义:

image

咳咳……不知不觉就开错了车,吁~及时的刹住。

JavaScript 中的 promise 是用于处理 异步 问题的 工具。没错,它就是一个 工具。跟你做饭用的炒勺和锅铲是同一个定义。

它可以将繁琐的异步问题通过更加易读方式来处理,从而提高代码的可阅读性,减少维护所带来的成本。

小栗子走一波:

传统调用:

function change1(data) {
  // 处理data……
  change2(data)
  function change2(data) {
    // 处理data……
    change3(data)
    function change3(data) {
      // 处理data……
        change4(data)
        function change4(data) {
        // 处理data……
            change5(data)
            function change5(data) {
          // 处理data……
                change6(data)
                function change6(data) {
                    ………………
                    }
                }
            }
        }
    }
}

Promise调用:

Promise.resolve(data)
.then(data => data) // 处理data
.then(data => data) // 处理data
.then(data => data) // 处理data
.then(data => data) // 处理data
.then(data => data) // 处理data
.then(data => data) // 处理data
.then(data => data) // 处理data
………………

先抛开其他所有的都不说,这一排整整齐齐的代码队伍,看上去就赏心悦目。

接下来,我们可以更进一步了。

2.同步和异步

刚才我们提到了,promise 是用来处理 异步 问题的工具。什么是 异步? 不是 异步 的问题又是什么?小朋友,你是否有很多问号 ?????

任务分为 异步 和 同步,不说官方的定义了,通过一段讲解来说吧。

2.1 什么是同步

同步:列举一个场景,假设你现在在银行办理业务,如果你前面的那个人办理不完,肯定是不能给你办理的。同一时间,业务员只能处理一个业务,当上一个业务到达完成状态(也就是处理完)的时候,才能接受下一个业务。

2.2 什么是异步

异步:同样,列举一个场景,场景变换到饭店,接待你的是一位服务员,服务员点完菜之后就会将菜单交到厨师手里,然后就可以继续接待下一位顾客。并不需要等到当前顾客的服务结束。不需要等待复杂的、消耗时间的炒菜操作的结束。

多么简单的道理,茅塞顿开的感觉有木有……

装13的话就不多说了,正经起来~

3.为什么会出现promise

要解释这个问题,得先弄懂什么是回调地狱的问题,回调地狱出现的原因是因为异步和回调函数。以吃披萨这件事为栗来说,想吃披萨必须得经历以下几步:

想吃披萨 --> 得有披萨店 --> 店里得有厨师 --> 厨师得有食材 --> 菜市场得出售食材 --> 收获制作食材的原材料

其中任何一步有问题,我都不可能吃到披萨。(这里拒绝抬杠。怕了怕了)

以代码的形式出现:

function yuancailiao() {
  // 收获原材料
  function chushou() {
    // 出售食材
    function shicai() {
      // 食材到披萨店里
      function chushi() {
        // 食材到厨师手中
        function dameile() {
          // 披萨店制作披萨
          function chi () {
            // 历经千辛万苦,我终于吃到了披萨。
          }
        }
      }
    }
  }
}

当嵌套的层级太深,就会在查找代码的时候出现晕眩的感觉,让你出现晕眩的代码就成为回调地狱

Promise.resolve('披萨店')
.then(data => data) // 购买原材料 
.then(data => data) // 制作披萨
.then(data => data) // 上桌
.then(data => data) // 开始大快朵eat
………………

哇哦~空气都变得清新了。(仿佛同样的代码我写了两次~~~希望能不被打)

这也就是promise出现的原因。

4.promise的三种状态

promise 在运行的过程中会出现三种状态。

  • pending 初始状态 表示待定
  • fulfilled 操作成功
  • rejected 操作失败

状态只能从 pending 到fulfilled 或者 rejected 。不可逆。

图解:

image

5.实现一个简单的promise

终于到了手撸代码的环节。准备好了吗?come on baby……

写代码之前我们先看下promise都有哪些需要实现的功能

  • 可使用new实例化
  • 接收一个函数作为参数
  • 函数中有 resolve reject 方法
  • 三种状态
  • 可使用 .then .catch 操作
  • 可抛出错误或执行错误都可通过 .catch 进行捕获
  • 链式调用

好像没有其他的了,先写。

第一步:定义构造函数,修改状态

// 先定义一个Promise构造函数
function Promise (executor) {
  // 定义一个状态,初始值为pending
  this.status = 'pending'

  // 成功方法
  this.success = () => {}

  // 失败方法
  this.error = () => {}

  // 定义resolve函数
  function resolve(data) {
    if (this.status === 'pending') {
      this.status = 'fulfilled'
      // 成功后执行成功方法,并将获取的数据过去
      this.success(data)
    }
  }
  // 定义reject函数
  function reject (errorMsg) {
    if (this.status === 'pending') {
      this.status = 'rejected'
      this.error(errorMsg)
    }
  }

  // 将 resolve 和 reject 传入到入参函数中,并 --->  绑定this !!!
  executor(resolve.bind(this), reject.bind(this))
}

第二步:定义then、catch方法

// 定义 then 方法,接收两个函数作为参数 success = () => {}, error = () => {}
Promise.prototype.then = function (success, error) {
  this.success = success
  this.error = error
}

// 定义 catch 方法
Promise.prototype.catch = function (error) {
  this.error = error
}

好,一个崭新的Promise就完成了。自信一试。

new Promise1((res, rej) => {
  res(1)
}).then((data) => {
  console.log(data)
})
// 什么也没输出。

纳尼。!跟我的预想完全不相符啊。机智如我怎么可能被一个小bug绊住了双脚。一阵紧锣密鼓的调试,终于让我发现了问题的所在。在resolve方法执行的时候,this.success还未被赋值。改成下面这样就可以正常输出了。

new Promise1((res, rej) => {
  setTimeout(() => {
    res(1)
  }, 0)
}).then((data) => {
  console.log(data)
})

介于上述问题,我们写出了另一版。请看代码:

// 先定义一个Promise1构造函数
  function Promise1 (executor) {
    // 定义一个状态,初始值为pending
    this.status = 'pending'

    // 成功方法
    this.successList = []
    //
    // 失败方法
    this.errorList = []

    // 添加 value缓存 --- 新添加 ---
    this.value = null

    // 定义resolve函数
    function resolve(data) {
      if (this.status === 'pending') {
        this.status = 'fulfilled'
        // 成功后执行成功方法,并将获取的数据过去
        // --- 新添加 ---
        this.value = data
        this.successList.forEach(cb => cb(data))
      }
    }
    // 定义reject函数
    function reject (errorMsg) {
      if (this.status === 'pending') {
        this.status = 'rejected'
        // --- 新添加 ---
        this.value = errorMsg
        this.errorList.forEach(cb => cb(errorMsg))
      }
    }

    // 将 resolve 和 reject 传入到入参函数中,并 --->  绑定this !!!
    executor(resolve.bind(this), reject.bind(this))
  }
  // 定义 then 方法,接收两个函数作为参数 success = () => {}, error = () => {}
  Promise1.prototype.then = function (success, error) {
    // 如果执行时状态已经更改,直接拿取缓存的值
    if (this.status === 'fulfilled') {
      success(this.value)
    }
    if (this.status === 'rejected') {
      error(this.value)
    }
    // 否则将当前函数保存
    if (this.status === 'pending') {
      this.successList.push(success)
      this.errorList.push(error)
    }
  }

  // 定义 catch 方法
  Promise1.prototype.catch = function (error) {
    if (this.status === 'pending') {
      this.error = error
      return
    }
    error(this.value)
  }

  new Promise1((res, rej) => {
    setTimeout(() => {
      res(1)
    }, 0)
  }).then((data) => {
    console.log(data)
  })

原谅我的罪过,让大家一次看这么多代码,实属是我的问题。以后尽量改正。

第三步:错误捕获

promise接受的参数可以显示的抛出错误,所以我们需要将错误捕获。很简,捕获到executor的错误就可以

try {
  // 将 resolve 和 reject 传入到入参函数中,并 --->  绑定this !!!
  executor(resolve.bind(this), reject.bind(this))
} catch (e) {
  reject(e)
}

第四步:链式调用

链式调用实现的核心是需要在 then 方法中返回一个新的promise。这里有一个需要注意的点。resolve的数据要使用resolve函数包裹,reject的数据要使用reject函数包裹。

Promise1.prototype.then = function (success, error) {
  // 如果执行时状态已经更改,直接拿取缓存的值
  if (this.status === 'fulfilled') {
    return new Promise1((resolve, reject) => {
      try{
        resolve(success(this.value))
      }catch(e) {
        reject(e)
      }
    })
  }
  if (this.status === 'rejected') {
    return new Promise1((resolve, reject) => {
      try{
        resolve(error(this.value))
      }catch(e) {
        reject(e)
      }
    })
  }
  // 否则将当前函数保存
  if (this.status === 'pending') {
    return new Promise1((resolve, reject) => {
      this.successList.push(() => {
        try {
          resolve(success(this.value))
        } catch (e) {
          reject(e)
        }
      })
      this.errorList.push(() => {
        try {
          resolve(error(this.value))
        } catch (e) {
          reject(e)
        }
      })
    })
  }
}

测试一下:

new Promise1((res, rej) => {
  setTimeout(() => {
    res(1)
  }, 0)
}).then((data) => {
  console.log(data)
}).then(() => {
  console.log(2)
}).then(() => {
  console.log(3)
})

ok,大功告成。

6.实现promise.all 和 promise.race 方法

6.1 promise.all方法的实现

promise.all方法的特点

  • 接收一个数组,数组中的元素为 promise 对象
  • 全部执行完成后,返回得到的结果
  • 只要有一个报错,直接返回错误信息。

栗子实现:

Promise.newAll = arr => {
  let result = [];
  return new Promise(function (resolve, reject) {
    let count = 0;
    arr.forEach(item => {
      item.then((res) => { // arr中为promise的列表,所以直接执行then方法。
        result.push(res)
        count ++
                // 如果全部成功,通过resolve返回得到的结果
        if (count === arr.length) {
          resolve(result)
        }
      }).catch((e) => {
        // 只要有一个报错,就执行reject
        reject(e)
      })
    })
  })
};

6.2 promise.race 方法

promise.race方法的特点

  • 接收一个数组,数组中的元素为 promise 对象
  • 返回最快得到的内容
  • 所有内容都失败后执行reject

栗子实现:

Promise.newRace = arr => {
  return new Promise(function (resolve, reject) {
    let count = 0;
    arr.forEach(item => {
      item.then((res) => { // arr中为promise的列表,所以直接执行then方法。
        // 返回最快得到的内容
        resolve(res)
      }).catch((e) => {
        count ++
                // 如果全部失败,通过resolve返回得到的结果
        if (count === arr.length) {
          reject(e)
        }
      })
    })
  })
};

7.async await 简单介绍

不知不觉又写成了老学究,还是太年轻。

现在介绍一下上面这两个家伙吧。

async 和 await是JavaScript提供的异步编程的语法糖(能甜死你的那种)。

先看小栗子

function see(){
  return new Promise((resolve)=>{
    setTimeout(()=>{
      resolve(1)
    },3000)
  })
}
async function test(){
  let result = await see()
  console.log(result)
}
test()

async 会将一个函数标记为异步函数,通过await等待结果的返回,并且他们还会改变事件循环 (文章敬请期待) 的执行顺序。并且。它们还可以使用 try {} catch() {} 捕获错误。

又是呕心沥血写的一篇文章。看完文章的你。理解了什么呢?

img


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK