回调函数

思路

在调用栈中将回调函数作为参数传入异步函数,异步任务执行完后,调用这个回调函数

  1. // 实现
  2. function f1(cb){
  3. setTimeout(function(){
  4. let data='数据'
  5. cb(data)
  6. },1000)
  7. }
  8. // 使用
  9. f2=function(data){console.log(data)}
  10. f1(f2)

优点

简单粗暴、容易理解

缺点

调用异步函数时需要向异步函数里面传递回调,代码耦合度太高,不利于代码维护;
嵌套的异步容易引起回调地狱

事件监听

思路

采用事件驱动的模式

在异步函数上挂上三个属性:
listeners:一个对象,用属性名记录事件,属性值是个数组,存放回调函数
on:往listeners放事件和回调函数
trigger:触发事件,执行listeners中的回调函数

在调用栈中调用 异步函数.on 绑定事件和回调

在异步函数里面触发事件即可

  1. //实现
  2. const f1 = () => setTimeout(()=>{
  3. let data='数据'
  4. f1.trigger('done',data) // 执行完函数体部分 触发done事件
  5. },1000)
  6. /*----------------核心代码start--------------------------------*/
  7. // listeners 用于存储f1函数各种各样的事件类型和对应的处理函数
  8. f1.listeners = {}
  9. // on方法用于绑定监听函数,type表示监听的事件类型,callback表示对应的处理函数
  10. f1.on = function (type,callback){
  11. if(!this.listeners[type]){
  12. this.listeners[type] = []
  13. }
  14. this.listeners[type].push(callback) //用数组存放 因为一个事件可能绑定多个监听函数
  15. }
  16. // trigger方法用于触发监听函数 type表示监听的事件类型
  17. f1.trigger = function (type,data){
  18. if(this.listeners&&this.listeners[type]){
  19. // 依次执行绑定的函数
  20. for(let i = 0;i < this.listeners[type].length;i++){
  21. const fn = this.listeners[type][i]
  22. fn(data)
  23. }
  24. }
  25. }
  26. /*----------------核心代码end--------------------------------*/
  27. //使用
  28. const f2 =(data) =>{ console.log(data) }
  29. const f3 = (data) =>{ console.log(data) }
  30. f1.on('done',f2) // 绑定done事件回调函数
  31. f1.on('done',f3) // 多个回调
  32. f1()// 一秒后输出 f1, f3,再一秒后输出f2

优点

直接调用异步函数,无需传递回调,避免了直接使用回调的高耦合问题

缺点

由事件驱动,不容易看出执行的主流程;嵌套的异步容易引起回调地狱

发布订阅

思路

声明一个全局对象Message,把事件监听方案中挂在异步函数上的三个属性转移到Message上
Message:{
listeners:事件监听方案中的listeners,
subscribe:事件监听方案中的on,
publish:事件监听方案中的trigger
}

  1. // 实现
  2. // 消息中心对象
  3. const Message = {
  4. listeners:{}
  5. }
  6. // subscribe方法用于添加订阅者 类似事件监听中的on方法 里面的代码完全一致
  7. Message.subscribe = function (type,callback){
  8. if(!this.listeners[type]){
  9. this.listeners[type] = []
  10. }
  11. this.listeners[type].push(callback) //用数组存放 因为一个事件可能绑定多个监听函数
  12. }
  13. // publish方法用于通知消息中心发布特定的消息 类似事件监听中的trigger 里面的代码完全一致
  14. Message.publish = function (type){
  15. if(this.listeners&&this.listeners[type]){
  16. // 依次执行绑定的函数
  17. for(let i = 0;i < this.listeners[type].length;i++){
  18. const fn = this.listeners[type][i]
  19. fn()
  20. }
  21. }
  22. }
  23. const f1 = () => setTimeout(()=>{
  24. console.log('f1')
  25. Message.publish('done') // 消息中心发出done信号
  26. },1000)
  27. //使用
  28. const f2 = () => console.log('f2')
  29. const f3 = () => console.log('f3')
  30. Message.subscribe('done',f2) // f2函数 订阅了done信号
  31. Message.subscribe('done',f3) // f3函数 订阅了done信号
  32. f1() // 执行结果和上面完全一样

优点

在事件监听方案中,是使用异步函数绑定;在发布订阅方案中,是使用全局对象绑定,有这个全局对象的存在,可以更方便的查看全局的消息订阅情况

缺点

不容易看出执行的主流程;嵌套的异步容易引起回调地狱

promise(ES6)

思路

实现一个promise对象,里面有三个状态pending,resolved,rejected和一个then方法,then方法会返回一个新的promise对象,这样就可以实现链式调用then方法;
当异步完成时,状态修改,自动执行then方法里面对应的回调;
如果回调里有嵌套的异步逻辑,就把嵌套的这个异步逻辑用新的promise包装一下返回,当这个嵌套的异步完成时,新的promise的状态修改,会自动执行后面then方法里面对应的回调;
这样就可以链式调用嵌套的异步了

  1. //实现
  2. (手写promise
  3. const f1 = new Promise(function (resolve, reject) {
  4. setTimeout(function(){
  5. resolve(100) // 承诺达成
  6. // reject(new Error('promise rejected')) // 承诺失败
  7. },1000)
  8. })
  9. //使用
  10. const f2 = () => setTimeout(()=>{consolelog('f2'); return 3})
  11. const f3 = () => console.log('f3')
  12. f1.then(function (value) {
  13. console.log('resolved', value)
  14. return new Promise((resolve,reject)=>{
  15. return f2
  16. })
  17. }, function (error) {
  18. console.log('rejected', error)
  19. }).then(function (value) {
  20. console.log('resolved', value)
  21. f3()
  22. }, function (error) {
  23. console.log('rejected', error)
  24. })

优点

链式调用避免回调地狱

缺点

可读性依然不如同步代码

generator(ES7)

思路

使用生成器函数可以暂停执行的特点,把promise链条截断,像使用同步代码一样使用异步函数

  1. //实现
  2. const g=main()
  3. const result = g.next()
  4. result.value.then(data => {
  5. const result2 = g.next(data)
  6. if (result2.done) return
  7. result2.value.then(data => {
  8. const result3 = g.next(data)
  9. if (result3.done) return
  10. result3.value.then(data => {
  11. g.next(data)
  12. })
  13. })
  14. })
  15. // 上面的代码可以使用递归实现,如下:
  16. function co (generator) {
  17. const g = generator()
  18. function handleResult (result) {
  19. if (result.done) return // 生成器函数结束
  20. result.value.then(data => {
  21. handleResult(g.next(data))
  22. }, error => {
  23. g.throw(error)
  24. })
  25. }
  26. handleResult(g.next())
  27. }
  28. co(main)
  29. //使用
  30. function * main () {
  31. try {
  32. const users = yield ajax('/api/users.json')
  33. console.log(users)
  34. const posts = yield ajax('/api/posts.json')
  35. console.log(posts)
  36. const urls = yield ajax('/api/urls11.json')
  37. console.log(urls)
  38. } catch (e) {
  39. console.log(e)
  40. }
  41. }

优点

增强了可读性

缺点

需要执行生成器函数生成生成器对象,然后嵌套的调用生成器对象的next方法

async/await(ES8)

思路

promise+generator+co的语法糖

  1. async function main () {
  2. try {
  3. const users = await ajax('/api/users.json')
  4. console.log(users)
  5. const posts = await ajax('/api/posts.json')
  6. console.log(posts)
  7. const urls = await ajax('/api/urls.json')
  8. console.log(urls)
  9. } catch (e) {
  10. console.log(e)
  11. }
  12. }