JavaScript回调函数&Promise总结「下」
文章分为上下两部分。上篇讲了为什么要使用回调函数和怎么写回调函数。
上一篇链接:JavaScript回调函数&Promise总结「上」
下篇讲优化回调函数。回调函数解决异步调用2个问题。
- 顺序不确定。▶︎ 使用回调函数解决顺序 ▶︎ 副作用回调函数嵌套地狱问题
- 返回结果无法取回。
1. 为什么要优化回调函数
1-1 解决异步返回值顺序不定的问题
异步操作,返回值的顺序是不知道的,至于按照的是什么顺序,那么就要看系统机制,系统机制这种飘忽不定的东西肯定是不行的,那么如何让返回值按照我们想要的顺序呢。
比如b()函数的操作需要a()函数的结果,但是a,b都是异步操作,那么如何让b拿到a的异步操作结果呢。
可以用回调函数嵌套的方法。下面上代码。▼
先做一个小 demo,异步读取3个 txt 文件。readFile()
是异步操作的函数。
// 版本1 回调函数读取3个文件,顺序不保证 const fs = require('fs') const path = require('path') fs.readFile(path.join(__dirname, "./1.txt"),'utf8',function(err,data1){ if(err){ throw err } console.log(data1) }) fs.readFile(path.join(__dirname, "./2.txt"),'utf8',function(err,data2){ if(err){ throw err } console.log(data2) }) fs.readFile(path.join(__dirname, "./3.txt"),'utf8',function(err,data3){ if(err){ throw err } console.log(data3) })
1-2 回调函数嵌套解决顺序问题
回调函数嵌套可以搞定顺序问题,保证1,2,3这样的输出
// 版本2 回调函数嵌套读取3个文件。保证1,2,3顺序。 const fs = require('fs') const path = require('path') fs.readFile(path.join(__dirname, "./1.txt"),'utf8',function(err,data1){ if(err){ throw err } console.log(data1) // 1.txt fs.readFile(path.join(__dirname, "./2.txt"),'utf8',function(err,data2){ if(err){ throw err } console.log(data2) // 2.txt fs.readFile(path.join(__dirname, "./3.txt"),'utf8',function(err,data3){ if(err){ throw err } console.log(data3) // 3.txt }) }) })
1-3 回调嵌套地狱
调用1个返回值还好,如果是3个,或者n个呢。比如异步同时打开3个文件。如下。
readFile()
函数为异步操作。
这样一层一层的调用下去岂不是很恶心吗?
为了解决这个问题,就引申出来了 JavaScript 的一个很好用的专门解决回调函数的 API Promise
2. Promise
- Promise 是 EcmaScript6 之后新增的构造函数。
- new 之后容器一旦创建就立即执行代码
- Promise并没有给你提供一个新的语法,只是对于原有语法的封装。
- 默认状态是
pending
,成功过就是resolve,错误就是reject。
直接写一下Promise怎么写吧。
// 版本3 Promise是什么引入 new Promise(function(resolve, reject){ fs.readFile(path.join(__dirname, "./1.txt"),'utf8',(err, data)=>{ if(err){ reject(err) // 错误 } else { resolve(data) // 成功 console.log(data) } }) })
如果把上面那个小demo按照Promise的写法版本是这样的。
// 版本4 开始使用Promise进行多个异步操作 const fs = require('fs') const path = require('path') // p1 let p1 = new Promise(function(resolve, reject){ fs.readFile(path.join(__dirname,"./1.txt"),"utf8",function(err, data1){ if(err){reject(err)} else {resolve(data1)} }) }) // p2 let p2 = new Promise(function(resolve, reject){ fs.readFile(path.join(__dirname,"./2.txt"),"utf8",function(err, data2){ if(err){reject(err)} else {resolve(data2)} }) }) // p3 let p3 = new Promise(function(resolve, reject){ fs.readFile(path.join(__dirname,"./3.txt"),"utf8",function(err, data3){ if(err){reject(err)} else {resolve(data3)} }) })
如果优雅的顺序操作写法就要用到下面的then()链式编程。
2-1 then()
■ 基本用法
then()
的基本写法就是这样。
p1.then(function(data){结果},function(err){输出错误信息})
p1 .then(function(data){ console.log(data) return 'hello data' // ↓ 这里写什么 下面的then()链式编程返回什么 },function(err){ console.log("err is ",err) }) .then(function(data){ console.log(data) // ↑ hello data }) // 如果是return什么就返回什么的话 // 那么retunrn Promise 的实例对象 呢 p1 .then(function(data){ console.log(data) return p2 },function(err){ console.log("err is ",err) }) .then(function(data){ console.log(data) // 这里返回的就是p2实例的结果 })
■ 完整案例
按照上面的思路 then()
的写法进行对上面那个小demo的改进
// 版本5 then()进行优化异步操作 const fs = require('fs') const path = require('path') let p1 = new Promise(function(resolve,reject){ fs.readFile(path.join(__dirname,"./1.txt"),"utf8",function(err,data){ if(err){reject(err)} else {resolve(data)} }) }) let p2 = new Promise(function(resolve,reject){ fs.readFile(path.join(__dirname,"./2.txt"),"utf8",function(err,data){ if(err){reject(err)} else {resolve(data)} }) }) let p3 = new Promise(function(resolve,reject){ fs.readFile(path.join(__dirname,"./3.txt"),"utf8",function(err,data){ if(err){reject(err)} else {resolve(data)} }) }) p1 .then(function(data){ console.log('START') console.log(data) return p2 },function(err){console.log('err is ',err)}) .then(function(data){ //这里传入的data其实就是return p2里 console.log(data) return p3 }) .then(function(data){ console.log(data) console.log('END') })
■ 继续封装
上面写的已经ok了,new Promise那个部分代码太多,那么就封装一下。
// 版本6 封装new Promise() const fs = require('fs') const path = require('path') function pReadFile(filePath){ return new Promise(function(resolve,reject){ fs.readFile(path.join(__dirname,filePath),"utf8",function(err,data){ if(err){reject(err)} else {resolve(data)} }) }) } pReadFile("./1.txt") .then(function(data){ console.log("START") console.log(data) return pReadFile("./2.txt") }) .then(function(data){ console.log(data) return pReadFile("./3.txt") }) .then(function(data){ console.log(data) console.log("END") })
2-2 async
上面的 Promise 虽然解决了回调函数嵌套地狱的问题,但是看起来还是很臃肿。
EcmaScript 7 引入了 async
关键字转换为异步函数的功能。是解决异步编程的终极方案。
■ 基本写法
普通函数前面加上 async 就变成了异步函数
MDN参考:async_function
// 普通函数 const fn = () =>{} // 异步函数 const fn = async () => {} //写成这样也对 async function fn (){ }
其实 async 异步函数默认的返回的是一个 Promise 对象
async function fn () {} console.log(fn()) // Promise { undefined } // 异步函数的返回值就是在原有的函数基础上在外面包裹了一层Promise对象 async function fn () { return 'hello async'} console.log(fn()) // Promise { 'hello async' }
return 返回的 Promise 对象中 return 关键字就代替了 resolve()
方法
■ 普通函数 PK 异步函数
也就是说,异步函数内部,省了你 new Promise()
这个步骤了
// 普通函数Promise变异步操作 function fn(){ return new Promise(function(){ console.log("hello fn") }) } fn().then(function data(){ console.log(data) }) // hello fn // async异步函数直接写 async function fn(){ return "hello async" } fn().then(function(data){ console.log(data) }) //hello async
2-3 await
利用异步函数捕获异步操作错误的用法是这样写的
// ERROR 写法 async function fn (){ throw "ERROR" return "hello async" } fn().then(function(data){ console.log(data) }).catch(function(err){ console.log(err) }) // ERROR
throw
执行到错误地方停止执行并且返回 err 到下面进行输出。
这样写也太麻烦了,于是 await
又登场了,他有几个需要注意的地方。
- await 关键字只出现在异步函数中,其他函数没用。
- await 后面只能接上Promise 的 API,其他没用。
- await 可以暂停异步函数向下执行,直到 Promise 返回结果
■ 基本写法
async function p1 (){return "p1"} async function p2 (){return "p2"} async function p3 (){return "p3"} async function run(){ let r1 = await p1() let r2 = await p2() let r3 = await p3() console.log(r1) console.log(r2) console.log(r3) } run() // p1 p2 p3
剩下的以后再写。累了。
总结
从异步操作 ▶︎ 回调函数 ▶︎ Promise 整体思路
想一个函数不按照顺序来执行
▼
异步操作
▼
需要异步return的结果 因为异步结果在函数之前就return出来了,出来undefined
▼
需要回调函数
▼
回调函数副作用回调嵌套地狱 ( *`ω´)!!
▼
Promise的API来拯救
▼
API里有then()解决回调地狱脏不垃圾的写法
▼
使用async更加优雅
▼
最终大法await
函数内部异步操作的结果需要回调函数才能拿到!这就是使用回调函数的意义。
后面的一堆Promise的使用,都是为了解决回调函数地狱这个副作用所服务的。
共有评论(0)