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

编程之家收集整理的这篇文章主要介绍了39.JavaScript中Promise的基本概念、使用方法,回调地狱规避、链式编程编程之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

概述

本文是上篇《JavaScript异步与回调》的后继,建议先行阅读,以便理解本文的核心内容。异步是为了提高CPU的占用率,让其始终处于忙碌状态。有些操作(最典型的就是I/O)本身不需要CPU参与,而且非常耗时,如果不使用异步就会形成阻塞状态,CPU空转,页面卡死。在异步环境下发生I/O操作,CPU就把I/O工作扔一边(此时I/O由其他控制器接手,仍然在数据传输),然后处理下一个任务,等I/O操作完成后通知CPU(回调就是一种通知方式)回来干活。《JavaScript异步与回调》想要表达的核心内容是,异步工作的,下方主要介绍关于39.JavaScript中Promise的基本概念、使用方法,回调地狱规避、链式编程的全文内容,希望对你有所帮助。

在这里插入图片描述



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

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

一、前言

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

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

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

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

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

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

二、Promise基本概念

Promise可以翻译为“承诺”,我们可以通过把异步工作封装称一个Promise,也就是做出一个承诺,承诺在异步工作结束后给出明确的信号!

Promise语法:

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

通过以上语法,我们就可以把异步工作封装成一个Promise。在创建Promise时传入的函数就是处理异步工作方法,又被称为executor(执行者)。

resolvereject是由JavaScript自身提供的回调函数,当executor执行完了任务就可以调用

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

executor会在Promise创建完成后立即自动执行,其执行状态会改变Promise内部属性的状态:

2.1 异步工作的封装

文件模块的fs.readfile就是一个异步函数我们可以通过在executor中执行文件读取操作,从而实现对异步工作的封装。

以下代码封装了fs.readfile函数,并使用resolve(data)处理成功结果,使用reject(err)处理失败的结果。

代码如下:

let promise = new Promise((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执行的结果,就需要使用thencatchfinally三个方法

then

Promisethen方法可以用来处理Promise执行完成后的工作,它接收两个回调参数,语法如下:

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

举例:

let promise = new Promise((resolve, 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的回调传nullpromise.then(null,(err)=>{...})

亦或者采用更优雅的方式:promise.catch((err)=>{...})

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

.catch((err)=>{...})then(null,(err)=>{...})作用完全相同。

finally

.finallypromise不论结果如何都会执行的函数,和try...catch...语法中的finally用途一样,都可以处理和结果无关的操作。

例如:

new Promise((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', data) => {
    console.log(data.toString()) //1
    fs.readfile('2.txt', data) => {
        console.log(data.toString())
        fs.readfile('3.txt', data) => {
            console.log(data.toString())
            fs.readfile('4.txt', data) => {
                console.log(data.toString())
                fs.readfile('5.txt', data) => {
                    console.log(data.toString())
                    fs.readfile('6.txt', data) => {
                        console.log(data.toString())
                        fs.readfile('7.txt', data) => {
                            console.log(data.toString())
                            fs.readfile('8.txt', data) => {
                                console.log(data.toString())
                                fs.readfile('9.txt', data) => {
                                    console.log(data.toString())
                                    fs.readfile('10.txt', data) => {
                                        console.log(data.toString())
                                        // ==> 地狱之门
                                    })
                                })
                            })
                        })
                    })
                })
            })
        })
    })
})

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

3.2 不使用回调产生的后果

如果我们使用回调,直接把fs.readfile()顺序的按照如下代码调用一遍,会发生什么呢?

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

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

PS E:\Code\Node\demos\03-callback> node .\index.Js
1
2
3
4
6
9
5
7
10
8

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

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

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 = new Promise((resolve, data) => {
        if (err) reject(err)
        resolve(data)
    })
})
let promise2 = promise1.then(
    data => {
        console.log(data.toString())
        return new Promise((resolve, reject) => {
            fs.readfile('2.txt', data) => {
                if (err) reject(err)
                resolve(data)
            })
        })
    }
)
let promise3 = promise2.then(
    data => {
        console.log(data.toString())
        return new Promise((resolve, reject) => {
            fs.readfile('3.txt', data) => {
                if (err) reject(err)
                resolve(data)
            })
        })
    }
)
let promise4 = promise3.then(
    data => {
        console.log(data.toString())
        //.....
    }
)
... ...

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

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

3.4 链式编程

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

代码如下:

function myReadfile(path) {
    return new Promise((resolve, reject) => {
        fs.readfile(path, data) => {
            if (err) reject(err)
            console.log(data.toString())
            resolve()
        })
    })
}

myReadfile('1.txt')
    .then(data => { return myReadfile('2.txt') })
    .then(data => { return myReadfile('3.txt') })
    .then(data => { return myReadfile('4.txt') })
    .then(data => { return myReadfile('5.txt') })
    .then(data => { return myReadfile('6.txt') })
    .then(data => { return myReadfile('7.txt') })
    .then(data => { return myReadfile('8.txt') })
    .then(data => { return myReadfile('9.txt') })
    .then(data => { return myReadfile('10.txt') })

由于myReadfile方法会返回一个新的Promise我们可以直接执行.then方法,这种编程方式被称为链式编程

代码执行结果如下:

PS E:\Code\Node\demos\03-callback> node .\index.Js
1
2
3
4
5
6
7
8
9
10

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

注意:在每一步的.then方法中都必须返回一个新的Promise对象,否则接收到的将是上一个旧的Promise

这是因为每个then方法都会把它的Promise继续向下传递。

总结

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

总结

以上是编程之家为你收集整理的39.JavaScript中Promise的基本概念、使用方法,回调地狱规避、链式编程全部内容,希望文章能够帮你解决39.JavaScript中Promise的基本概念、使用方法,回调地狱规避、链式编程所遇到的程序开发问题。

如果觉得编程之家网站内容还不错,欢迎将编程之家网站推荐给程序员好友。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。

JavaScript文章

JavaScript一种直译式脚本语言,是一种动态类型、弱类型、基于原型的语言,内置支持类型。JavaScript广泛用于客户端的脚本语言,用来给HTML网页增加动态功能。
JavaScript 可以通过不同的方式来输出数据:使用 window.alert() 弹出警告框。 使用 document.write() 方法将内容写到 HTML 文档中。使用 innerHTML 写入到 HTML 元素。
今天项目需要判断客户输入的密码强度,规则只数字为弱,数字+字母为中级,判断大小写组合等,下面青岛星网跟大家分享下实现代码。
表单是我们项目开发过程中最常用的,jQuery怎么获取表单元素单选框、复选框、下拉菜单的值呢?今天跟大家分享下:jQuery表单元素radio取值checkbox取值select取值的方法大全。
数组是网站开发项目中经常会用到,那么怎么删除数值的数值?怎么替换数组的数值?怎么添加数值的数值呢?今天青岛星网跟大家分享:JS删除/替换/添加/数组值的方法大全。
我们在设计表单的时候,经常会有一些文本框要求只能输入数字,如金额文本框、年龄文本框、成本文本框等等,此类文本框除了数字不允许别的字符输入,JS怎么办判断呢?
JSON数据是现在比较流行的一种数据方式,很多工具接口返回的参数都是JSON数据格式的,今天青岛星网跟大家分享下:jquery解析JSON数据的方法。
js模式窗口是我们经常会用到的一种弹出窗口,例如:上传图片弹出窗口,成功后赋值图片名称给父窗口,青岛星网下面跟大家分享:js弹出窗口showModalDialog模式窗口使用方法
微信公众号搜索 “ 程序精选 ” ,选择关注!
微信公众号搜 "程序精选"关注