Home>Article>Web Front-end> Detailed introduction to the basic concepts and usage of Promise in JavaScript

Detailed introduction to the basic concepts and usage of Promise in JavaScript

WBOY
WBOY forward
2022-07-12 13:38:50 1354browse

本篇文章给大家带来了关于javascript的相关知识,其中主要整理了Promise的基本概念及使用方法的相关问题,包括了Promise基本概念、使用Promise解决回调地狱等等内容,下面一起来看一下,希望对大家有帮助。

Detailed introduction to the basic concepts and usage of Promise in JavaScript

【相关推荐:javascript视频教程web前端

一、前言

异步是为了提高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内部属性的状态:

  • 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执行的结果,就需要使用thencatchfinally三个方法。

then

Promisethen方法可以用来处理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的回调传nullpromise.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

.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', (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.js12346957108

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

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

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, 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.js12345678910

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

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

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

【相关推荐:javascript视频教程web前端

The above is the detailed content of Detailed introduction to the basic concepts and usage of Promise in JavaScript. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:csdn.net. If there is any infringement, please contact admin@php.cn delete