欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

【拉钩教育】大前端技术分享

程序员文章站 2022-07-03 17:15:16
Part1 · JavaScript【深度剖析】函数式编程与Js异步编程、手写Promise文章说明:本专栏内容为本人参加【拉钩大前端高新训练营】的学习笔记以及思考总结,学徒之心,仅为分享。如若有误,请在评论区支出,如果您觉得专栏内容还不错,请点赞、关注、评论。共同进步!上一篇:函子前情提要:上一篇中提到了【函子】,点击上方链接可查看。本篇主要从代码角度去了解primise,基本用法都写在注释内,若有疑问,欢迎留言九、JavaScript异步编程1.同步模式同步模式指的就是我们...

Part1 · JavaScript【深度剖析】

函数式编程与Js异步编程、手写Promise

文章说明:本专栏内容为本人参加【拉钩大前端高新训练营】的学习笔记以及思考总结,学徒之心,仅为分享。如若有误,请在评论区支出,如果您觉得专栏内容还不错,请点赞、关注、评论。

共同进步!

上一篇:函子

前情提要:

上一篇中提到了【函子】,点击上方链接可查看。
本篇主要从代码角度去了解primise,基本用法都写在注释内,若有疑问,欢迎留言

九、JavaScript异步编程

1.同步模式

同步模式指的就是我们代码中的任务依次执行,程序执行的顺序与代码的编写顺序一致。

以下代码为同步模式的代码,具体分析其执行顺序

console.log('Global begin')

function bar() {
    console.log('Bar task')
}

function foo() {
    console.log('Foo task')
    bar()
}

foo()
console.log('Global end')

// Global begin
// Foo task
// Bar task
// Global end

分析:

  • 首先分析代码结构,本段代码为同步模式,js在读取到代码时,先将一个(anonymous)匿名函数放到调用栈。

  • 在读取到第一行console.log(‘Global begin’)时,将其压到调用栈,随后去执行,当控制台打印出结果后,将其弹出调用栈,继续下一行代码;

  • 在读取到bar函数及foo函数时,由于其并未执行,因此调用栈内无执行任务;

  • 在读取到foo()函数调用时,首先将foo()压入调用栈,遇console.log(‘Foo task’)代码,将其压入调用栈,执行完毕后弹出调用栈。随后将bar()压入调用栈,程序去bar()函数内部解析,将console.log(‘Bar task’)压入调用栈。foo()函数执行完毕后,依次将bar()、foo()函数弹出调用栈;

  • 最后将console.log(‘Global end’)压入调用栈并执行,随后将其弹出,代码运行完成,将(anonymous)弹出调用栈,程序完全结束。

2.异步模式

在浏览器端,耗时很长的操作都应该异步执行,避免浏览器失去响应,最好的例子就是Ajax操作。在服务器端,"异步模式"甚至是唯一的模式,因为执行环境是单线程的,如果允许同步执行所有http请求,服务器性能会急剧下降,很快就会失去响应。

异步调用并不会阻止代码的顺序执行,而是在将来的某一个时刻触发设置好的逻辑,所以我们

  1. 并不知道逻辑什么时候会被调用
  2. 只能定义当触发的时候逻辑是什么
  3. 只能等待,同时可以去处理其他的逻辑
console.log('global begin')
setTimeout(function timer1() {
    console.log('timer1 invoke')
}, 1800)
setTimeout(function timer2() {
    console.log('timer2 invoke')
    setTimeout(function inner() {
        console.log('inner invoke')
    }, 1000)
}, 1000)
console.log('global end')

// global begin
// global end
// timer2 invoke
// timer1 invoke
// inner invoke

分析:

首先分析代码结构,本段代码为异步模式,js在读取到代码时,先将一个(anonymous)匿名函数放到调用栈。

将第一行console.log(‘global begin’)压入调用栈并执行后弹出,此时控制台打印global begin;

程序到setTimeout时,首先将setTimeout(timer1)压入调用栈,在web API线程放入timer1计时器,倒计时1.8s,随后将setTimeout(timer1)弹出调用栈;

同上步,将setTimeout(timer2)压入调用栈,web API线程放入timer2计时器,倒计时1s,随后将setTimeout(timer2)弹出调用栈;

随后将console.log(‘global end’)压入调用栈并执行后弹出调用栈,代码执行完毕,将anonymous弹出调用栈;

web API将timer1与timer2依次放入事件队列,此时timer2优先倒计时完毕,进入调用栈,然后执行内部代码。将console.log(‘timer2 invoke’)压入调用栈并执行后弹出。

随后遇setTimeout(inner),将其压入调用栈并在Web API加入inner计时器,倒计时1s。setTimeout(inner)弹出调用栈。

此时随倒计时,timer1倒计时完毕,程序进入timer1内部,将console.log(‘timer1 invoke’)压入调用栈并执行后弹出。

随后inner()计时器进入任务队列,在倒计时结束后,压入调用栈并执行后弹出。至此,程序执行完毕。

---------插入图片

3.回调函数

回调函数指的是需要在将来不确定的某一时刻异步调用的函数。通常,在这种回调函数中,我们经常需要频繁地访问外部数据。

function foo(callback) {
    setTimeout(function () {
        callback()
    }, 3000)
}

foo(function () {
    console.log('这就是一个回到函数')
    console.log('调用者定义这个函数,执行者执行这个函数')
    console.log('起始就是调用者告诉执行者:异步任务结束后应该做什么')
})

4.Promise概述

由于上述回调函数可以存在嵌套关系,因此容易导致回调地狱问题,即产生如下代码:

$.get('url1', function (data1) {
    $.get('url2', function (data2) {
        $.get('url3', function (data3) {
            $.get('url4', function (data4) {
                $.get('url5', function (data5) {
                    $.get('url6', function (data6) {
                        $.get('url7', function (data7) {
                            // 略微夸张,但实际存在
                        })
                    })
                })
            })
        })
    })
})

因此提出Promise承诺,即在回调函数中,承诺在异步完成后下一步干什么。

Promise存在三种状态,及承诺开始pending,承诺兑现fulfilled以及承诺失败rejected。并且,承诺的状态一旦确定就不可在被改变,即当状态为fulfilled时,此promise的状态就不可再变为rejected,反之同样。

fulfilled与rejected存在onFulfilled和onRejected状态。

5.Promise基本用法

// Promise 基本示例

const promise = new Promise(function (resolve, reject) {
  // 这里用于“兑现”承诺

  // resolve(100) // 承诺达成

  reject(new Error('promise rejected')) // 承诺失败
})

promise.then(function (value) {
  // 即便没有异步操作,then 方法中传入的回调仍然会被放入队列,等待下一轮执行
  console.log('resolved', value)
}, function (error) {
  console.log('rejected', error)
})

console.log('end')

6.Promise使用案例

// Promise 方式的 AJAX

function ajax (url) {
  // 返回一个promise对象
  return new Promise(function (resolve, reject) {
    var xhr = new XMLHttpRequest()
    xhr.open('GET', url)
    xhr.responseType = 'json'
    xhr.onload = function () {
      if (this.status === 200) {
        resolve(this.response)
      } else {
        reject(new Error(this.statusText))
      }
    }
    xhr.send()
  })
}

ajax('/api/foo.json').then(function (res) {
  console.log(res)  // ajax请求成功后调用
}, function (error) {
  console.log(error)  // ajax请求失败后调用
})

7.Promise常见误区

promise最常见的误区的就是嵌套使用:

function ajax(url) {
    return new Promise(function (resolve, reject) {
        var xhr = new XMLHttpRequest()
        xhr.open('GET', url)
        xhr.responseType = 'json'
        xhr.onload = function () {
            if (this.status === 200) {
                resolve(this.response)
            } else {
                reject(new Error(this.statusText))
            }
        }
        xhr.send()
    })
}
ajax('/api/urls.json').then(function (urls) {
    ajax(urls.users).then(function (users) {
        ajax(urls.users).then(function (users) {
            ajax(urls.users).then(function (users) {
                ajax(urls.users).then(function (users) {

                })
            })
        })
    })
})

嵌套使用就失去了Promise通过状态变化获取结果的优势,依然无法获取其中某个回调是否完成的结果。

8.Promise链式调用

// Promise 链式调用

function ajax(url) {
    return new Promise(function (resolve, reject) {
        var xhr = new XMLHttpRequest()
        xhr.open('GET', url)
        xhr.responseType = 'json'
        xhr.onload = function () {
            if (this.status === 200) {
                resolve(this.response)
            } else {
                reject(new Error(this.statusText))
            }
        }
        xhr.send()
    })
}

// var promise = ajax('/api/users.json')

// var promise2 = promise.then(
//   function onFulfilled (value) {
//     console.log('onFulfilled', value)
//   },
//   function onRejected (error) {
//     console.log('onRejected', error)
//   }
// )

// console.log(promise2 === promise)

ajax('/api/users.json')
    .then(function (value) {
        console.log(1111)
        return ajax('/api/urls.json')
    }) // => Promise
    .then(function (value) {
        console.log(2222)
        console.log(value)
        return ajax('/api/urls.json')
    }) // => Promise
    .then(function (value) {
        console.log(3333)
        return ajax('/api/urls.json')
    }) // => Promise
    .then(function (value) {
        console.log(4444)
        return 'foo'
    }) // => Promise
    .then(function (value) {
        console.log(5555)
        console.log(value)
    })

9.Promise异常处理

// Promise 异常处理

function ajax (url) {
  return new Promise(function (resolve, reject) {
    // foo()
    // throw new Error()
    var xhr = new XMLHttpRequest()
    xhr.open('GET', url)
    xhr.responseType = 'json'
    xhr.onload = function () {
      if (this.status === 200) {
        resolve(this.response)
      } else {
        reject(new Error(this.statusText))
      }
    }
    xhr.send()
  })
}

// ajax('/api/users11.json')
//   .then(function onFulfilled (value) {
//     console.log('onFulfilled', value)
//   }, function onRejected (error) {
//     console.log('onRejected', error)
//   })

// 使用 catch 注册失败回调是更常见的

// ajax('/api/users11.json')
//   .then(function onFulfilled (value) {
//     console.log('onFulfilled', value)
//   })
//   .catch(function onRejected (error) {
//     console.log('onRejected', error)
//   })

// then(onRejected) 实际上就相当于 then(undefined, onRejected)

// ajax('/api/users11.json')
//   .then(function onFulfilled (value) {
//     console.log('onFulfilled', value)
//   })
//   .then(undefined, function onRejected (error) {
//     console.log('onRejected', error)
//   })

// 同时注册的 onRejected 只是给当前 Promise 对象注册的失败回调
// 它只能捕获到当前 Promise 对象的异常

// ajax('/api/users.json')
//   .then(function onFulfilled (value) {
//     console.log('onFulfilled', value)
//     return ajax('/error-url')
//   }, function onRejected (error) {
//     console.log('onRejected', error)
//   })

// 因为 Promise 链条上的任何一个异常都会被一直向后传递,直至被捕获
// 分开注册的 onRejected 相当于给整个 Promise 链条注册失败回调

ajax('/api/users.json')
  .then(function onFulfilled (value) {
    console.log('onFulfilled', value)
    return ajax('/error-url')
  }) // => Promise {}
  // .catch(function onRejected (error) {
  //   console.log('onRejected', error)
  // })

// 全局捕获 Promise 异常,类似于 window.onerror
window.addEventListener('unhandledrejection', event => {
  const { reason, promise } = event

  console.log(reason, promise)
  // reason => Promise 失败原因,一般是一个错误对象
  // promise => 出现异常的 Promise 对象

  event.preventDefault()
}, false)

// Node.js 中使用以下方式
// process.on('unhandledRejection', (reason, promise) => {
//   console.log(reason, promise)
//   // reason => Promise 失败原因,一般是一个错误对象
//   // promise => 出现异常的 Promise 对象
// })

10.Promise静态方法

// 常用 Promise 静态方法

function ajax (url) {
  return new Promise(function (resolve, reject) {
    // foo()
    // throw new Error()
    var xhr = new XMLHttpRequest()
    xhr.open('GET', url)
    xhr.responseType = 'json'
    xhr.onload = function () {
      if (this.status === 200) {
        resolve(this.response)
      } else {
        reject(new Error(this.statusText))
      }
    }
    xhr.send()
  })
}

// Promise.resolve('foo')
//   .then(function (value) {
//     console.log(value)
//   })

// new Promise(function (resolve, reject) {
//   resolve('foo')
// })

// 如果传入的是一个 Promise 对象,Promise.resolve 方法原样返回

// var promise = ajax('/api/users.json')
// var promise2 = Promise.resolve(promise)
// console.log(promise === promise2)

// 如果传入的是带有一个跟 Promise 一样的 then 方法的对象,
// Promise.resolve 会将这个对象作为 Promise 执行

// Promise.resolve({
//   then: function (onFulfilled, onRejected) {
//     onFulfilled('foo')
//   }
// })
// .then(function (value) {
//   console.log(value)
// })

// Promise.reject 传入任何值,都会作为这个 Promise 失败的理由

// Promise.reject(new Error('rejected'))
//   .catch(function (error) {
//     console.log(error)
//   })

Promise.reject('anything')
  .catch(function (error) {
    console.log(error)
  })

11.Promise并行执行

// Promise 并行执行

function ajax (url) {
  return new Promise(function (resolve, reject) {
    // foo()
    // throw new Error()
    var xhr = new XMLHttpRequest()
    xhr.open('GET', url)
    xhr.responseType = 'json'
    xhr.onload = function () {
      if (this.status === 200) {
        resolve(this.response)
      } else {
        reject(new Error(this.statusText))
      }
    }
    xhr.send()
  })
}

// ajax('/api/users.json')
// ajax('/api/posts.json')

// var promise = Promise.all([
//   ajax('/api/users.json'),
//   ajax('/api/posts.json')
// ])

// promise.then(function (values) {
//   console.log(values)
// }).catch(function (error) {
//   console.log(error)
// })

// ajax('/api/urls.json')
//   .then(value => {
//     const urls = Object.values(value)
//     const tasks = urls.map(url => ajax(url))
//     return Promise.all(tasks)
//   })
//   .then(values => {
//     console.log(values)
//   })

// Promise.race 实现超时控制

const request = ajax('/api/posts.json')
const timeout = new Promise((resolve, reject) => {
  setTimeout(() => reject(new Error('timeout')), 500)
})

Promise.race([
  request,
  timeout
])
.then(value => {
  console.log(value)
})
.catch(error => {
  console.log(error)
})

12.Promise执行时序

// 微任务

console.log('global start')

// setTimeout 的回调是 宏任务,进入回调队列排队
setTimeout(() => {
  console.log('setTimeout')
}, 0)

// Promise 的回调是 微任务,本轮调用末尾直接执行
Promise.resolve()
  .then(() => {
    console.log('promise')
  })
  .then(() => {
    console.log('promise 2')
  })
  .then(() => {
    console.log('promise 3')
  })

console.log('global end')

// Promise vs. Callback

function ajax (url, callback) {
  const executor = (resolve, reject) => {
    var xhr = new XMLHttpRequest()
    xhr.open('GET', url)
    xhr.responseType = 'json'
    xhr.onload = () => {
      if (xhr.status === 200) {
        resolve(xhr.response)
      } else {
        reject(new Error(xhr.statusText))
      }
    }
    xhr.send()
  }

  if (typeof callback === 'function') {
    // support callback
    executor(
      res => callback(null, res),
      err => callback(error)
    )
  }

  return new Promise(executor)
}

// ajax('/api/urls.json', (error, value) => {
//   if (error) {
//     return console.error(error)
//   }
//   console.log(value)
// })

// ajax('/api/urls.json')
//   .then(value => {
//     console.log(value)
//   })
//   .catch(error => {
//     console.error(error)
//   })

// Callback hell
ajax('/api/url1', (error, value) => {
  ajax('/api/url2', (error, value) => {
    ajax('/api/url3', (error, value) => {
      ajax('/api/url4', (error, value) => {

      })
    })
  })
})

// Promise chain
ajax('/api/url1')
  .then(value => {
    return ajax('ajax/url2')
  })
  .then(value => {
    return ajax('ajax/url3')
  })
  .then(value => {
    return ajax('ajax/url4')
  })
  .catch(error => {
    console.error(error)
  })

// sync mode code
try {
  const value1 = ajax('/api/url1')
  console.log(value1)
  const value2 = ajax('/api/url2')
  console.log(value2)
  const value3 = ajax('/api/url3')
  console.log(value3)
  const value4 = ajax('/api/url4')
  console.log(value4)
} catch (e) {
  console.error(e)
}

13.Generator异步方案

// 生成器函数回顾

function * foo () {
  console.log('start')

  try {
    const res = yield 'foo'
    console.log(res)
  } catch (e) {
    console.log(e)
  }
}

const generator = foo()

const result = generator.next()
console.log(result)


// generator.next('bar')

generator.throw(new Error('Generator error'))

// Generator 配合 Promise 的异步方案

function ajax (url) {
  return new Promise((resolve, reject) => {
    var xhr = new XMLHttpRequest()
    xhr.open('GET', url)
    xhr.responseType = 'json'
    xhr.onload = () => {
      if (xhr.status === 200) {
        resolve(xhr.response)
      } else {
        reject(new Error(xhr.statusText))
      }
    }
    xhr.send()
  })
}

function * main () {
  try {
    const users = yield ajax('/api/users.json')
    console.log(users)

    const posts = yield ajax('/api/posts.json')
    console.log(posts)

    const urls = yield ajax('/api/urls11.json')
    console.log(urls)
  } catch (e) {
    console.log(e)
  }
}

function co (generator) {
  const g = generator()

  function handleResult (result) {
    if (result.done) return // 生成器函数结束
    result.value.then(data => {
      handleResult(g.next(data))
    }, error => {
      g.throw(error)
    })
  }

  handleResult(g.next())
}

co(main)

// const result = g.next()

// result.value.then(data => {
//   const result2 = g.next(data)

//   if (result2.done) return

//   result2.value.then(data => {
//     const result3 = g.next(data)

//     if (result3.done) return

//     result3.value.then(data => {
//       g.next(data)
//     })
//   })
// })

14.Async方案

// Async / Await 语法糖

function ajax (url) {
  return new Promise((resolve, reject) => {
    var xhr = new XMLHttpRequest()
    xhr.open('GET', url)
    xhr.responseType = 'json'
    xhr.onload = () => {
      if (xhr.status === 200) {
        resolve(xhr.response)
      } else {
        reject(new Error(xhr.statusText))
      }
    }
    xhr.send()
  })
}

function co (generator) {
  const g = generator()

  function handleResult (result) {
    if (result.done) return // 生成器函数结束
    result.value.then(data => {
      handleResult(g.next(data))
    }, error => {
      g.throw(error)
    })
  }

  handleResult(g.next())
}

async function main () {
  try {
    const users = await ajax('/api/users.json')
    console.log(users)

    const posts = await ajax('/api/posts.json')
    console.log(posts)

    const urls = await ajax('/api/urls.json')
    console.log(urls)
  } catch (e) {
    console.log(e)
  }
}

// co(main)
const promise = main()

promise.then(() => {
  console.log('all completed')
})

本篇主要从代码角度去了解primise,基本用法都写在注释内,若有疑问,欢迎留言

本文地址:https://blog.csdn.net/weixin_42122355/article/details/109379872