0


39.JavaScript中Promise的基本概念、使用方法,回调地狱规避、链式编程

在这里插入图片描述

  • 《JavaScript再出发》系列文章阅读
  • 《仙宗》发布招仙贴,广招天下道友

文章目录

JavaScript中Promise的基本概念、使用方法,以及回调地狱规避

本文是上篇《JavaScript异步与回调》的后继,建议先行阅读,以便理解本文的核心内容。

一、前言

异步是为了提高CPU的占用率,让其始终处于忙碌状态。

有些操作(最典型的就是I/O)本身不需要CPU参与,而且非常耗时,如果不使用异步就会形成阻塞状态,CPU空转,页面卡死。

在异步环境下发生I/O操作,CPU就把I/O工作扔一边(此时I/O由其他控制器接手,仍然在数据传输),然后处理下一个任务,等I/O操作完成后通知CPU(回调就是一种通知方式)回来干活。

《JavaScript异步与回调》想要表达的核心内容是,异步工作的具体结束时间是不确定的,为了准确的在异步工作完成后进行后继的处理,就需要向异步函数中传入一个回调,从而在完成工作后继续下面的任务。

虽然回调可以非常简单的实现异步,但是却会由于多重嵌套形成回调地狱。避免回调地狱就需要解嵌套,将嵌套编程改为线性编程。

Promise

JavaScript

中处理回调地狱最优解法。

二、Promise基本概念

Promise

可以翻译为“承诺”,我们可以通过把异步工作封装称一个

Promise

,也就是做出一个承诺,承诺在异步工作结束后给出明确的信号!

Promise

语法:

let promise =newPromise(function(resolve,reject){// 异步工作})

通过以上语法,我们就可以把异步工作封装成一个

Promise

。在创建

Promise

时传入的函数就是处理异步工作的方法,又被称为

executor

(执行者)。

resolve

reject

是由

JavaScript

自身提供的回调函数,当

executor

执行完了任务就可以调用:

  • resolve(result)——如果成功完成,并返回结果result
  • reject(error)——如果执行是失败并产生error
executor

会在

Promise

创建完成后立即自动执行,其执行状态会改变

Promise

内部属性的状态:

  • state——最初是pending,然后在resolve被调用后转为fulfilled,或者在reject被调用时变为rejected
  • result——最初时undefined,然后在resolve(value)被调用后变为value,或者在reject被调用后变为error;

2.1 异步工作的封装

文件模块的

fs.readFile

就是一个异步函数,我们可以通过在

executor

中执行文件读取操作,从而实现对异步工作的封装。

以下代码封装了

fs.readFile

函数,并使用

resolve(data)

处理成功结果,使用

reject(err)

处理失败的结果。

代码如下:

let promise =newPromise((resolve, reject)=>{
    fs.readFile('1.txt',(err, data)=>{
        console.log('读取1.txt')if(err)reject(err)resolve(data)})})

如果我们执行这段代码,就会输出“读取1.txt”字样,证明在创建

Promise

后立刻就执行了文件读取操作。

Promise

内部封装的通常都是异步代码,但是并不是只能封装异步代码。

2.2 Promise执行结果获取

以上

Promise

案例封装了读取文件操作,当完成创建后就会立即读取文件。如果想要获取

Promise

执行的结果,就需要使用

then

catch

finally

三个方法。

then

Promise

then

方法可以用来处理

Promise

执行完成后的工作,它接收两个回调参数,语法如下:

promise.then(function(result),function(error))
  • 第一个回调函数用于处理成功执行后的结果,参数result就是resolve接收的值;
  • 第二个回调函数用于处理失败执行后的结果,参数error就是reject接收的参数;

举例:

let promise =newPromise((resolve, reject)=>{
    fs.readFile('1.txt',(err, data)=>{
        console.log('读取1.txt')if(err)reject(err)resolve(data)})})
promise.then((data)=>{
        console.log('成功执行,结果是'+ data.toString())},(err)=>{
        console.log('执行失败,错误是'+ err.message)})

如果文件读取成功执行,会调用第一个函数:

PS E:\Code\Node\demos\03-callback>node .\index.js
读取1.txt
成功执行,结果是1

删掉

1.txt

,执行失败,就会调用第二个函数:

PS E:\Code\Node\demos\03-callback>node .\index.js
读取1.txt
执行失败,错误是ENOENT: no such file or directory, open'E:\Code\Node\demos\03-callback\1.txt'

如果我们只关注成功执行的结果,可以只传入一个回调函数:

promise.then((data)=>{
    console.log('成功执行,结果是'+ data.toString())})

到这里我们就是实现了一次文件的异步读取操作。

catch

如果我们只关注失败的结果,可以把第一个

then

的回调传

null

promise.then(null,(err)=>{...})

亦或者采用更优雅的方式:

promise.catch((err)=>{...})
let promise =newPromise((resolve, reject)=>{
    fs.readFile('1.txt',(err, data)=>{
        console.log('读取1.txt')if(err)reject(err)resolve(data)})})
promise.catch((err)=>{
    console.log(err.message)})
.catch((err)=>{...})

then(null,(err)=>{...})

作用完全相同。

finally

.finally

promise

不论结果如何都会执行的函数,和

try...catch...

语法中的

finally

用途一样,都可以处理和结果无关的操作。

例如:

newPromise((resolve,reject)=>{//something...}).finally(()=>{console.log('不论结果都要执行')}).then(result=>{...},err=>{...})
  • finally回调没有参数,不论成功与否都会执行
  • finally会传递promise的结果,所以在finally后仍然可以.then

三、使用Promise解决回调地狱

3.1 回调地狱出现的场景

现在,我们有一个需求:使用

fs.readFile()

方法顺序读取10个文件,并把十个文件的内容顺序输出。

由于

fs.readFile()

本身是异步的,我们必须使用回调嵌套的方式,代码如下:

fs.readFile('1.txt',(err, data)=>{
    console.log(data.toString())//1
    fs.readFile('2.txt',(err, data)=>{
        console.log(data.toString())
        fs.readFile('3.txt',(err, data)=>{
            console.log(data.toString())
            fs.readFile('4.txt',(err, data)=>{
                console.log(data.toString())
                fs.readFile('5.txt',(err, data)=>{
                    console.log(data.toString())
                    fs.readFile('6.txt',(err, data)=>{
                        console.log(data.toString())
                        fs.readFile('7.txt',(err, data)=>{
                            console.log(data.toString())
                            fs.readFile('8.txt',(err, data)=>{
                                console.log(data.toString())
                                fs.readFile('9.txt',(err, data)=>{
                                    console.log(data.toString())
                                    fs.readFile('10.txt',(err, data)=>{
                                        console.log(data.toString())// ==> 地狱之门})})})})})})})})})})

虽然以上代码能够完成任务,但是随着调用嵌套的增加,代码层次变得更深,维护难度也随之增加,尤其是我们使用的是可能包含了很多循环和条件语句的真实代码,而不是例子中简单的

console.log(...)

3.2 不使用回调产生的后果

如果我们不使用回调,直接把

fs.readFile()

顺序的按照如下代码调用一遍,会发生什么呢?

//注意:这是错误的写法
fs.readFile('1.txt',(err, data)=>{
    console.log(data.toString())})
fs.readFile('2.txt',(err, data)=>{
    console.log(data.toString())})
fs.readFile('3.txt',(err, data)=>{
    console.log(data.toString())})
fs.readFile('4.txt',(err, data)=>{
    console.log(data.toString())})
fs.readFile('5.txt',(err, data)=>{
    console.log(data.toString())})
fs.readFile('6.txt',(err, data)=>{
    console.log(data.toString())})
fs.readFile('7.txt',(err, data)=>{
    console.log(data.toString())})
fs.readFile('8.txt',(err, data)=>{
    console.log(data.toString())})
fs.readFile('9.txt',(err, data)=>{
    console.log(data.toString())})
fs.readFile('10.txt',(err, data)=>{
    console.log(data.toString())})

以下是我测试的结果(每次执行的结果都是不一样的):

PS E:\Code\Node\demos\03-callback>node .\index.js
12346957108

产生这种非顺序结果的原因是异步,并非多线程并行,异步在单线程里就可以实现。

之所以在这里使用这个错误的案例,是为了强调异步的概念,如果不理解为什么会产生这种结果,一定要回头补课了!

3.3 Promise解决方案

使用

Promise

解决异步顺序文件读取的思路:

  1. 封装一个文件读取promise1,并使用resolve返回结果
  2. 使用promise1.then接收并输出文件读取结果
  3. promise1.then中创建一个新的promise2对象,并返回
  4. 调用新的promise2.then接收并输出读取结果
  5. promise2.then中创建一个新的promise3对象,并返回
  6. 调用新的promise3.then接收并输出读取结果

代码如下:

let promise1 =newPromise((resolve, reject)=>{
    fs.readFile('1.txt',(err, data)=>{if(err)reject(err)resolve(data)})})let promise2 = promise1.then(data=>{
        console.log(data.toString())returnnewPromise((resolve, reject)=>{
            fs.readFile('2.txt',(err, data)=>{if(err)reject(err)resolve(data)})})})let promise3 = promise2.then(data=>{
        console.log(data.toString())returnnewPromise((resolve, reject)=>{
            fs.readFile('3.txt',(err, data)=>{if(err)reject(err)resolve(data)})})})let promise4 = promise3.then(data=>{
        console.log(data.toString())//.....})......

这样我们就把原本嵌套的回调地狱写成了线性模式。

但是代码还存在一个问题,虽然代码从管理上变的美丽了,但是大大增加了代码的长度。

3.4 链式编程

以上代码过于冗长,我们可以通过两个步骤,降低代码量:

  • 封装功能重复的代码,完成文件读取和输出工作
  • 省略中间promise的变量创建,将.then链接起来

代码如下:

functionmyReadFile(path){returnnewPromise((resolve, reject)=>{
        fs.readFile(path,(err, data)=>{if(err)reject(err)
            console.log(data.toString())resolve()})})}myReadFile('1.txt').then(data=>{returnmyReadFile('2.txt')}).then(data=>{returnmyReadFile('3.txt')}).then(data=>{returnmyReadFile('4.txt')}).then(data=>{returnmyReadFile('5.txt')}).then(data=>{returnmyReadFile('6.txt')}).then(data=>{returnmyReadFile('7.txt')}).then(data=>{returnmyReadFile('8.txt')}).then(data=>{returnmyReadFile('9.txt')}).then(data=>{returnmyReadFile('10.txt')})

由于

myReadFile

方法会返回一个新的

Promise

,我们可以直接执行

.then

方法,这种编程方式被称为链式编程

代码执行结果如下:

PS E:\Code\Node\demos\03-callback>node .\index.js
12345678910

这样就完成了异步且顺序的文件读取操作。

注意:在每一步的

.then

方法中都必须返回一个新的

Promise

对象,否则接收到的将是上一个旧的

Promise

这是因为每个

then

方法都会把它的

Promise

继续向下传递。

总结

  1. Promise基本概念
  2. Promise的结果获取
  3. 回调地狱的解决
  4. 链式编程

本文转载自: https://blog.csdn.net/weixin_43302112/article/details/125711176
版权归原作者 @魏大大 所有, 如有侵权,请联系我们删除。

“39.JavaScript中Promise的基本概念、使用方法,回调地狱规避、链式编程”的评论:

还没有评论