javascript 异步编程
采用单线程模式工作
最早js语言运行在浏览器中, 利用dom操作去实现页面交互,所以就决定了他是单线程工作模式,否则就会出现多线程复杂的问题(DOM动作不知道是哪一个线程操作的)
一个人执行任务,如果有多个任务就排队, 让这个人一个一个的去执行
单线程的优缺点
优点就是安全, 简单, 缺点就是因为一个任务一个任务执行,其中一个任务耗时特别长, 就会出现类似假死的情况(无限循环)
为了解决这个问题, js有两种任务执行模式同步模式(Synchronous), 异步模式(Aynchronous)
异步编程的主要概要
- 同步模式和异步模式
- 事件循环和消息队列(js如何实现异步)
- 异步编程的几种方式
- promise异步方案 宏任务/微任务队列
- Generator异步方案 async/await 语法
同步模式和异步模式
同步模式 : 指的是任务执行顺序和代码书写顺序一致,一个执行完再执行下一个(任务执行完才会往下执行)
异步模式 : 不会等待这个任务执行完成,而是开启任务后就立即往后执行下个任务, 耗时函数的后续逻辑通过回调函数来定义,在内部, 耗时任务完成后会就会自动执行传入的回调函数(下达这个任务开启的指令之后代码就会继续执行,不会等待任务结束)
js线程某个时刻发起一个异步调用, 他紧接着继续执行其他的任务,此时异步线程会单独执行异步任务, 执行过后会将回调放在消息队列里, js主线程执行完任务过后会依次调用消息队列里的任务
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//除了调用栈,还用到了消息队列和事件循环
回调函数 —— 所有异步编程方案的基础
回调函数: 由调用者定义,交给执行者执行的函数
promise —— 一种更优的异步编程统一方案
- 回调函数是异步编程的基础,但我们直接使用传统回调函数去完成复杂的异步逻辑,就无法避免大量的回调函数嵌套的问题,导致回调地狱的问题.
- CommonJS社区提出了Promise的规范,ES6中称为语言规范
- Promise是一个对象,用来表述一个异步任务执行之后是成功还是失败。
promise封装ajax
function ajax(url) {return new Promise((resolve, reject) => {// 创建一个XMLHttpRequest对象来发送请求const xhr = new XMLHttpRequest();//设置请求方式,请求地址xhr.open('GET', url)// 设置返回数据格式为json对象xhr.responseType = 'json'xhr.onload = () => {this.status === '200' ?resolve(this.response) :reject(new Error(this.statusText))}// 开始执行异步请求xhr.send()})}
promise的本质
本质上也是使用回调函数的方式去定义异步任务结束后所需要执行的任务. 回调函数是通过then传递过去的
promise链式调用
常见误区
- 嵌套使用的方式是promise最常见的误区,要使用链式调用来使异步任务扁平化
链式调用的理解
promise对象的then方法
- 返回一个全新的promise对象,可以继续调用then方法
- 返回一个值,作为resolve的值传递
- 没有值,默认是undefined
- 后面的then方法就是在为上一个then返回的promise注册回调函数
- 前面then方法中回调函数的返回值会作为后面then方法的参数
- 如果回调返回的是promise,后面then方法的回调会等待他的结束
promise异常处理
then中回调的onRejected方法
.catch(推荐)
.catch(() => {})相当于.then(undefined, () => {})
.catch和onRejected异常捕获的区别:
- catch是对上一个.then()返回的promise进行处理,不过第一个promise的错误也能顺延到catch中, 而.then()的第二个参数, 只能捕获上一个promise的报错, 当前then的resolve函数处理中错误捕获不到.
所以.catch相当于是给整个promise链条注册的一个失败回调函数.推荐使用
全局对象上的unhandledrejection事件
还可以在全局对象上注册一个unhandledrejection事件,处理那些代码中没有被手动捕获的promise异常,当然并不推荐使用。
更合理的是:在代码中明确捕获每一个可能的异常,而不是丢给全局处理
//reason => Promise 失败原因,一般是一个错误对象//promise => 出现异常的Promise对象// 浏览器window.addEventListener('unhandledrejection', event => {const { reason, promise } = eventconsole.log(reason, promise)event.preventDefault()}, false)// nodeprocess.on('unhandledRejection', (reason, promise) => {console.log(reason, promise)})
手写promise源码
原理分析
- promise就是一个类
在执行类的时候需要传递一个执行器,执行器会立即执行
- promise有三种状态, 成功-fulfilled, 失败-rejected, 等待-pending
pengding => fulfilled
pengding => rejected
状态一经确认不可更改
- resolve 和 reject 函数是用来更改状态的
resolve - fulfilled
reject - rejected
- then内部就是做状态判断的, 成功调用成功回调, 失败调用失败回调
then方法是定义在原型对象中的
- then成功后参数表示成功之后的值 失败之后参数表示失败理由
- then多次调用添加多个处理函数, 用数组统计保存,shift删除调用(每个方法互不影响)
- then链式(then需要返回一个promise对象)
then链式调用的参数是上一个then方法的返回值
- then方法返回的promise要判断是否是他本身, 是则报错阻止程序进行
- then返回值判断(普通值和promise对象)
普通值直接resolve
promise对象则判断其返回值情况 resolve(value) / reject(reason)
- then 可选参数
successcallback 不存在则 value => value
failcalback 不存在则 reason => { throw reason }
