Promise的引出
封装node方法,传入文件路径,可以读取文件并将内容返回
1 const fs = require('fs') 2 const path = require('path') 3 4 //封装函数 5 function getFileByPath(fpath) { 6 fs.readFile(fpath, 'utf-8', (err, dataStr) => { 7 if (err) throw err 8 return dataStr 9 }) 10 11 } 12 13 //readFile为异步处理函数,放到队列里继续运行主程序,直接跳到第10行代码。函数执行发现没有return,默认返回undefined,所以上述函数运行结果都是undefined 14 15 var data = getFileByPath(path.join(__dirname, './files/1.txt')) 16 console.log(data) //undefined
所以上述方法不能拿到文件的数据,进一步改造函数,使用回调:
function getFileByPath(fpath, callback) { fs.readFile(fpath, 'utf-8', (err, dataStr) => { if (err) throw err callback(dataStr) }) } getFileByPath(path.join(__dirname, './files/1.txt'), (data) => { console.log(data) })
上面函数,如果传入的文件类型不正确就不能执行后面的代码,可以将文件路径错误时的处理再进一步优化,将错误结果也进行返回
//将错误结果也进行返回,并规定成功的结果位于回调函数的第二个位置,失败的结果位于回调函数的第一个位置 function getFileByPath(fpath, callback) { fs.readFile(fpath, 'utf-8', (err, dataStr) => { if (err) return callback(err) callback(null, dataStr) }) } getFileByPath(path.join(__dirname, './files/1.txt'), (err, data) => { if (err) { console.log(err.message) } else { console.log(data) } })
回调函数将成功与失败的结果都在一起处理进行优化,将两种结果分成两个回调函数
function getFileByPath(fpath, succCb, errCb) { fs.readFile(fpath, 'utf-8', (err, dataStr) => { if (err) return errCb(err) succCb(dataStr) }) } getFileByPath(path.join(__dirname, './files/11.txt'), data => { console.log('成功的结果为' + data) }, err => { console.log('失败的结果' + err.message) })
如果有一个需求为,先读取文件1,再读取文件2,最后再读取文件3,调用函数如下
getFileByPath(path.join(__dirname, './files/1.txt'), data => { console.log(data) getFileByPath(path.join(__dirname, './files/2.txt'), data => { console.log(data) getFileByPath(path.join(__dirname, './files/3.txt'), data => { console.log(data) }) }) })
这种函数作为参数层层嵌套称为回调地狱
使用es6中的Promise来解决回调地狱的问题(使用Promise并不会减少代码量,只是将多层的嵌套改成了代码串联的形式)
Promise概念
1、promise是一个构造函数,可以通过new创建实例
2、在Promise上有两个函数,resolve(成功之后的回调函数)和reject(失败之后的回调函数)
3、在Promise构造函数的prototype属性上,有then方法。通过Promise创造的实例都可以使用then方法
4、每一个Promise实例表示一个异步操作
5、通过then方法指定回调函数时,成功的回调函数必须传,失败的回调函数可以不传
6、Promise实例只要被创建,就会立即执行里面的异步方法
var promise = new Promise(function () { fs.readFile(path.join(__dirname, './files/2.txt'), 'utf-8', (err, dataStr) => { if (err) throw err; console.log(dataStr) }) }) //node运行后,会打印出文件内容,说明异步函数被执行 //也就是说,在使用new关键字后,除了得到Promise实例,还会立即调用我们为Promise构造函数传递的function,执行function中的异步操作代码
如果不想使函数在调用时才执行,可以将Promise包裹在另一个函数中
function getFileByPath(fpath) { var promise = new Promise(function () { fs.readFile(fpath, 'utf-8', (err, dataStr) => { if (err) throw err; console.log(dataStr) }) }) } getFileByPath(path.join(__dirname, './files/2.txt'))
如果想获得文件读取的结果,直接在readFile中return会像之前函数封装一样,因为异步操作并不能拿到结果,所以需要借助回调函数。Promise为我们提供了成功回调resolve与失败回调reject,在new Promise的函数参数中,传入resolve与reject,代码如下
function getFileByPath(fpath) { var promise = new Promise(function (resolve, reject) { fs.readFile(fpath, 'utf-8', (err, dataStr) => { if (err) return reject(err); resolve(dataStr) }) }) } //resolve与reject为形参
通过then方法可以指定成功和失败的回调,需要通过Promise的实例调用方法,所以需要将上面函数中的promise返回
function getFileByPath(fpath) { var promise = new Promise(function (resolve, reject) { fs.readFile(fpath, 'utf-8', (err, dataStr) => { if (err) return reject(err); resolve(dataStr) }) }) return promise }
通过下面方法调用
var p = getFileByPath(path.join(__dirname, './files/2.txt')) p.then(data => { console.log('读取成功' + data) }, err => { console.log('读取失败' + err.message) })
使用上面的Promise函数完成“先读取文件1,再读取文件2,最后再读取文件3”需求
getFileByPath(path.join(__dirname, './files/1.txt')).then(data => { console.log('文件1读取成功,开始读取文件2数据') getFileByPath(path.join(__dirname, './files/2.txt')).then(data => { console.log('文件2读取成功,开始读取文件3数据') getFileByPath(path.join(__dirname, './files/3.txt')).then(data => { console.log('文件3读取成功') }) }) })
可以看到上面的函数依然是层层嵌套,因为这并不是Promise正确的打开方式。优化代码如下:
Promise的链式调用
new Promise((resolve, reject) => { setTimeout(() => { resolve('aaa') }, 1000) }).then(res => { console.log(`${res},第一层代码`) return new Promise(resolve => { resolve(res + '111') }) }).then(res => { console.log(`${res},第二层代码`) return new Promise(resolve => { resolve(res + '222') }) }).then(res => { console.log(`${res},第三次代码`) })
可这样简写
new Promise((resolve, reject) => { setTimeout(() => { resolve('aaa') }, 1000) }).then(res => { console.log(`${res},第一层代码`) return Promise.resolve(res + '111') }).then(res => { console.log(`${res},第二层代码`) return Promise.resolve(res + '222') }).then(res => { console.log(`${res},第三次代码`) })
更加简洁的方式
new Promise((resolve, reject) => { setTimeout(() => { resolve('aaa') }, 1000) }).then(res => { console.log(`${res},第一层代码`) return res + '111' }).then(res => { console.log(`${res},第二层代码`) return res + '222' }).then(res => { console.log(`${res},第三次代码`) })
Promise捕获异常
上面代码中并没有对异常进行处理。当其中一个路径不正确时,例如将1.txt替换成11.txt,其中11.txt是不存在的
getFileByPath(path.join(__dirname, './files/11.txt')).then(data => { console.log('文件1读取成功,开始读取文件2数据' + data) return getFileByPath(path.join(__dirname, './files/2.txt')) }).then(data => { console.log('文件2读取成功,开始读取文件3数据' + data) return getFileByPath(path.join(__dirname, './files/3.txt')) }).then(data => { console.log('文件3读取成功' + data) }) console.log('OK')
上面代码执行后,会先打印出'OK'再报错。因为报错所在的函数是异步操作。
1、如果前面的Promise执行失败后不影响后面的Promise执行,这样做:
getFileByPath(path.join(__dirname, './files/11.txt')).then(data => { console.log('文件1读取成功,开始读取文件2数据' + data) return getFileByPath(path.join(__dirname, './files/2.txt')) }, err => { console.log('这是失败的信息' + err.message) //需要return一个新的Promise不影响后面的.then return getFileByPath(path.join(__dirname, './files/2.txt')) }).then(data => { console.log('文件2读取成功,开始读取文件3数据' + data) return getFileByPath(path.join(__dirname, './files/3.txt')) }).then(data => { console.log('文件3读取成功' + data) })
2、如果后续的Promise执行依赖于前面的Promise结果,前面的Promise失败后,则立即终止所有的Promise执行,通过.catch
getFileByPath(path.join(__dirname, './files/11.txt')).then(data => { console.log('文件1读取成功,开始读取文件2数据' + data) return getFileByPath(path.join(__dirname, './files/2.txt')) }).then(data => { console.log('文件2读取成功,开始读取文件3数据' + data) return getFileByPath(path.join(__dirname, './files/3.txt')) }).then(data => { console.log('文件3读取成功' + data) }).catch(err => { //如果前面有任何的Promise执行失败,会立即终止Promise执行,并进入catch中 console.log('这是错误处理'+err.message) })
Promise的三种状态
- pending :等待状态,比如正在进行网络请求或者定时器没有到时间
- fulfill : 满足状态,当我们主动回调了resolve时,就处于该状态,并且会回调.then()
- reject : 拒绝状态,当我们主动回调了reject时就处于该状态,并且会回调.catch()
Promise.all
需求:在两个请求都结束后进行xxx操作
普通回调
let isResult1 = false let isResult2 = false $.ajax({ url: 'url1', success(data) { isResult1 = true handleResult() } }) $.ajax({ url: 'url2', success(data) { isResult2 = true handleResult() } }) function handleResult() { if (isResult1 && isResult2) { //执行代码 } }
使用all
Promise.all([ new Promise((resolve, reject) => { $.ajax({ url: 'url1', success(data) { resolve(data) } }) }), new Promise((resolve, reject) => { $.ajax({ url: 'url2', success(data) { resolve(data) } }) }) ]).then(results => { results[0] //url1的结果 results[1] //url2的结果 })