后端小白的Promise学习笔记

本文是一篇关于JavaScript Promise的学习笔记,适合后端开发者。从Promise的基本概念、初上手案例、状态与对象值、API详解、重要注意点到async/await的使用,全方位解析Promise的运用。文章通过实例详细讲解了Promise的创建、状态转变、链式调用、Promise.all()、Promise.race()等,并探讨了Promise在解决回调地狱和异常处理上的优势。此外,还介绍了Node.js中的util.promisify方法,以及如何使用async/await简化异步代码。
摘要由CSDN通过智能技术生成

Promise

写在前面

本博文仅作为个人学习过程的记录,可能存在诸多错误,希望各位看官不吝赐教,支持错误所在,帮助小白成长!

一、认识Promise

Promise是什么?!

Promise是ES6提出的异步编程的一种新规范。

旧版本中完成异步任务都是通过简单的回调任务!

Promise解决什么问题?

由于JavaScript作为脚本语言,解释执行的特点所有的代码执行都是单线程,就导致有些操作需要借助异步操作来完成。而异步的任务的执行顺序难以控制。

例如下面的代码:(JavaScript中使用Node的内建模块fs操作文件就是异步任务):

const fs = require('fs')

fs.readFile('./1.txt', 'utf-8', (err, data) => {
   
  if (err) {
   
    throw err
  }
  console.log(data)
})

fs.readFile('./2.txt', 'utf-8', (err, data) => {
   
  if (err) {
   
    throw err
  }
  console.log(data)
})

fs.readFile('./3.txt', 'utf-8', (err, data) => {
   
  if (err) {
   
    throw err
  }
  console.log(data)
})

fs.readFile('./4.txt', 'utf-8', (err, data) => {
   
  if (err) {
   
    throw err
  }
  console.log(data)
})

代码并不会按照你所写代码的顺序执行,而是通过异步回调的方式执行,所以你就可能看到这样的结果:

image-20210524151708057

如果我们想要读取的顺序受我们控制,我们就只能在回调函数中嵌套另一个异步任务,就像这样:

const fs = require('fs')

fs.readFile('./1.txt', 'utf-8', (err, data) => {
   
    if (err) {
   
        throw err
    }
    console.log(data)
    fs.readFile('./2.txt', 'utf-8', (err, data) => {
   
        if (err) {
   
            throw err
        }
        console.log(data)
        fs.readFile('./3.txt', 'utf-8', (err, data) => {
   
            if (err) {
   
                throw err
            }
            console.log(data)
            fs.readFile('./4.txt', 'utf-8', (err, data) => {
   
                if (err) {
   
                    throw err
                }
                console.log(data)
            })
        })
    })
})

这样写的代码,输出的顺序永远是1,2,3,4,因为只有上一个文件的内容输出完成后才会启动下一个异步任务!

但是这个代码…,是不是看的头疼?!这就是**回调地狱!(callback hell)**有两个明显的缺陷:

  1. 难以阅读!
  2. 容易出错,且难以排查!

Promise的优势

  1. 允许链式调用,解决回调地狱问题
  2. 逻辑清晰,便于理解

二、Promise初上手

2.1、基本案例

我们先用一个最简单的例子,来演示一下Promise的使用:

就以上面读取文件为例:

首先我们创建一个Promise对象:

image-20210524154004453

哇擦,这个参数你一看,头皮发麻。
Promise的构造函数,需要一个函数executer(也即你的异步任务),而这个异步任务函数的参数是所两个函数,函数名分别为resolvereject(这是我推荐的一种命名规范!)

resolve函数:函数类型是(value) => void,先说一下,这个函数可以在你异步任务中调用,会将你的异步任务执行状态从待定(pending)转换为(fulfilled)成功!同时这个函数可以传入你异步任务的执行结果作为函数参数value

reject函数:函数类型的(reason) => void,与resolve函数相反,它会将你的异步任务执行状态从待定(pending)转为(rejected)失败!并且可以传入一个值作为你异步任务的出错的原因,然后在具体的reject函数实现中进行处理!

const fs = require('fs')

const promise = new Promise((resolve, reject) => {
   
  fs.readFile('./1.txt', 'utf-8', (err, data) => {
   
    if (err) {
   
      // 调用reject, 标识任务失败,同时传入err作为失败的reason
      reject(err)
    } else {
   
      // 调用resolve, 标识任务成功,传入data作为异步任务的执行结果
      resolve(data)
    }
  })
})

现在我们要知道几点:

  1. Promise的状态装换有两种:pending -> fulfilledpending -> rejected
  2. resolvereject是两个回调函数,分别会在调用时将你的异步任务状态修改为fulfilled、rejected。我们在异步任务中调用了它们,但是并没有为它们写具体的实现

目前我们的代码运行,没有任何动静

现在通过promise对象的then方法,对两种任务状态下的回调函数进行实现:

then方法的两个参数分别刚好与resolve、reject函数的类型相同:

image-20210524162118938

(并且then方法的返回值还是一个Promise对象,这是方便链式调用的独特设计!)

promise.then(
  // 任务转态为成功时,执行此函数, value为异步任务中调用resolve传入的执行结果
  (value) => {
   
    console.log(value)
  },
  // 任务状态为失败时,执行此函数,reason为你在异步任务中传入的失败原因reason
  (reason) => {
   
    console.log(reason)
  },
)

然后就可以开开心心测试代码啦~~

2.2、让小屁孩都能懂的解释

看完代码,不禁我都有疑问,为什么可以这样写?!这是个什么思路?!对于Java代码写的多的我,无法理解为什么!!

直到我看到了这篇文章:我以为我很懂Promise,直到我开始实现Promise/A+规范 - 知乎 (zhihu.com)

那么我们就从这项技术的命名开始说起,毕竟它的名字就能代表它的绝大部分思想的源泉!Promise,直译就是承诺!我们简称它为画饼。

那么我们创建Promise实例的过程也就是我们画饼的过程,这个具体的饼也就是Promise创建时我们传入的异步任务

我们画饼时就会强调后果:

  • 如果画出来了,各位就工资几何翻倍,别墅靠海。这也就是我们在“饼”中写下的resolve()
  • 如果饼没了,各位就天天加班,月月低保。这就是“饼”中的reject()

当然这都是在画饼,都还是虚构的,还没有真正实现,谁都不清楚“饼”的结果如何!即这个饼的状态还是pending状态,它有可能fulfilled,也有可能变成rejected!(这取决于这个“饼”内部的走向)

既然画了饼,那必然有人来接这个”饼“,如果饼实现了我就朝九晚五,房车不愁。如果饼没了打工仔我整日就以泪洗面。而这个接饼的过程就是我们常见的发布与订阅模式,而在Promise的概念中,订阅就是通过then方法来实现,所有的订阅者需要做好两方面的心理准备!这两手心理准备的实现就是在then方法的两个参数函数中体现。

除了一个饼可以多个人同时订阅外,一个饼还可以一层层往下传,也就是后面会学习的链式调用!比如老板先画了一个大饼给经理,然后经理又给部门组长画了一个饼,然后组长又给组员画了一个饼。这就是promise.then(..).then(..).then(..)的结果!

2.3、util.promisify

如果所有的异步任务,都需要我们进行手动包装为Promise版本,写起来还是比较麻烦的。于是Node中util模块中提供了一个函数promisify:传入一个遵循常见的错误优先的回调风格的函数(即以 (err, value) => ... 回调作为最后一个参数),并返回一个返回 promise 的版本。

我们先来搞清楚什么叫遵循常见的错误优先的回调风格的函数

  1. 函数参数中有一个回调函数
  2. 回调函数是这个函数最后一个参数
  3. 回调函数的参数列表中错误优先,例如(err, value) => { ... }

很显然,我们刚才使用的fs.readFile()就满足这个条件!!

那现在我们利用这个工具,来将其进行封装:

const util = require('util')
const fs = require('fs')

// 封装返回一个Promise对象
const promiseReadFile = util.promisify(fs.readFile)

promiseReadFile('./1.txt', 'utf8').then(
  (value) => {
   
    console.log(value)
  },
  (reason) => {
   
    console.log(reason)
  },
)

三、Promise状态与对象值

在基础案例中,我们说过Promise对象的三个状态:

  • pending
  • resolved/fulfilled
  • rejected

我可不是胡编乱造,看代码:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值