Home >Web Front-end >JS Tutorial >Detailed introduction to the basic concepts and usage of Promise in JavaScript
本篇文章给大家带来了关于javascript的相关知识,其中主要整理了Promise的基本概念及使用方法的相关问题,包括了Promise基本概念、使用Promise解决回调地狱等等内容,下面一起来看一下,希望对大家有帮助。
【相关推荐:javascript视频教程、web前端】
异步是为了提高CPU的占用率,让其始终处于忙碌状态。
有些操作(最典型的就是I/O)本身不需要CPU参与,而且非常耗时,如果不使用异步就会形成阻塞状态,CPU空转,页面卡死。
在异步环境下发生I/O操作,CPU就把I/O工作扔一边(此时I/O由其他控制器接手,仍然在数据传输),然后处理下一个任务,等I/O操作完成后通知CPU(回调就是一种通知方式)回来干活。
《JavaScript异步与回调》想要表达的核心内容是,异步工作的具体结束时间是不确定的,为了准确的在异步工作完成后进行后继的处理,就需要向异步函数中传入一个回调,从而在完成工作后继续下面的任务。
虽然回调可以非常简单的实现异步,但是却会由于多重嵌套形成回调地狱。避免回调地狱就需要解嵌套,将嵌套编程改为线性编程。
Promise
是JavaScript
中处理回调地狱最优解法。
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
;文件模块的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
内部封装的通常都是异步代码,但是并不是只能封装异步代码。
以上Promise
案例封装了读取文件操作,当完成创建后就会立即读取文件。如果想要获取Promise
执行的结果,就需要使用then
、catch
和finally
三个方法。
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())})
到这里我们就是实现了一次文件的异步读取操作。
如果我们只关注失败的结果,可以把第一个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
是promise
不论结果如何都会执行的函数,和try...catch...
语法中的finally
用途一样,都可以处理和结果无关的操作。
例如:
new Promise((resolve,reject)=>{ //something...}).finally(()=>{console.log('不论结果都要执行')}).then(result=>{...}, err=>{...})
finally
回调没有参数,不论成功与否都会执行finally
会传递promise
的结果,所以在finally
后仍然可以.then
现在,我们有一个需求:使用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(...)
。
如果我们不使用回调,直接把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
产生这种非顺序结果的原因是异步,并非多线程并行,异步在单线程里就可以实现。
之所以在这里使用这个错误的案例,是为了强调异步的概念,如果不理解为什么会产生这种结果,一定要回头补课了!
使用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()) //..... })... ...
这样我们就把原本嵌套的回调地狱写成了线性模式。
但是代码还存在一个问题,虽然代码从管理上变的美丽了,但是大大增加了代码的长度。
以上代码过于冗长,我们可以通过两个步骤,降低代码量:
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!