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

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

时间2025-06-16 16:49:51分类IT科技浏览5376
导读:《JavaScript再出发》系列文章阅读 《仙宗》发布招仙贴,广招天下道友 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 = new Promise(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 = 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执行的结果                    ,就需要使用then       、catch和finally三个方法。

then

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

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

举例:

let promise = new Promise((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 = new Promise((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用途一样                    ,都可以处理和结果无关的操作             。

例如:

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, (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 1 2 3 4 6 9 5 7 10 8

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

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

3.3 Promise解决方案

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

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

代码如下:

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

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

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

3.4 链式编程

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

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

代码如下:

function myReadFile(path) { return new Promise((resolve, reject) => { fs.readFile(path, (err, 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继续向下传递。

总结

Promise基本概念 Promise的结果获取 回调地狱的解决 链式编程
声明:本站所有文章       ,如无特殊说明或标注                    ,均为本站原创发布                    。任何个人或组织             ,在未征得本站同意时       ,禁止复制             、盗用       、采集                    、发布本站内容到任何网站             、书籍等各类媒体平台                    。如若本站内容侵犯了原著者的合法权益                    ,可联系我们进行处理。

创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!

展开全文READ MORE
linux ping6(详解Linux系统中ping和arping命令的用法) 一键生成原创文章网站(GPT-3高质量文章批量写作)