系列文章:
异步
只要把一段代码包装成一个函数。并指定它在响应某个事件时执行,就是在创建了一个将来执行的块。异步就是关于现在和将来的事件间隙。
事件循环
JS 引擎运行在宿主环境里,这些环境提供了一种机制来处理程序中多个块的执行,且执行每块时调用 JS 引擎,这种机制叫做事件循环。一旦有事情要运行,事件循环就会运行,直到队列清空,事件循环的每一轮称为一个 tick。
执行栈
js 在执行代码时会将不同的变量存于内存中的不同位置:堆里存放一些对象,栈中存放一些基础变量类型和变量以及对象的指针。
当调用一个方法时,js 会生成一个与这个方法对应的执行环境,即执行上下文,存放着这个方法的私有作用域、上层作用域的指向、方法的参数、方法内的变量、this对象等。因为js是单线程,所以这些方法排在一个单独的地方,这个地方就叫做执行栈。
js遇到一个异步事件后,将事件挂起,继续执行执行栈中的其他任务,当异步事件返回结果后,js会将这个事件加入和当前执行栈不同的另外一个队列(事件队列),当当前执行栈内所有任务执行完,主线程闲置后,查询事件队列是否有任务,如果有,继续加入执行栈中执行。
任务
异步任务分为两类:微任务和宏任务
宏任务:
- setInterval()
- setTimeout()
- setImmediate()
微任务:
- new Promise()
- new MutaionObserver()
- process.nextTick()
当前执行栈执行完毕时会立刻先处理所有微任务队列中的事件,然后再去宏任务队列中取出一个事件。同一次事件循环中,微任务永远在宏任务之前执行。
回调函数
回调函数就是一段可执行代码段,它作为一个参数传递给其他的代码。 回调函数是 JavaScript 异步的基本单元,但随着js越来越成熟,回调已经不够用了,它存在问题是:
- 回调表达异步流程方式是非线性的、非顺序的,这使得推导这样的代码难度大
- 回调回受到控制反转的原因,会导致一系列信任问题 所以我们需要更优秀的异步模式
Promise
Promise 的存在就可以解决回调存在的缺乏顺序性和可信任性的问题,它叫控制反转再次反转,从第三方的控制权再次移交回主程序,不给第三方传函数,而是从第三方获得某个东西,然后把回调传给这个东西,不再关心函数里具体的实现,关注于函数的状态,函数状态一旦变化,变成完成/拒绝后,就不会再变。
通常通过 thenable 鸭子类型检测作为 Promise 的识别方案 => 不要写 then 函数。
在使用promise时,有几点需要注意⚠️:
- 如果使用多个参数调用 resolve() 或者 reject(),第一个参数之后所有的参数都会默默忽略。
- promise 未决议时,如果 js 异常导致 promise 拒绝,也可以在 reject 的响应函数内处理。若一决议,不可以在此 promise 的reject 响应函数处理。
链式流程控制可行的 Promise 具备的特性:
- 调用 Promise 的 then() 会自动创建一个新的 Promise 从调用返回
- 在函数内部,如果返回一个值或抛出一个异常,新返回的Promise就相应的决议
- 函数若返回一个promise,不管它的决议值是什么,都会成为当前 then() 返回的 promise 的决议值
Promise 模式
基于 Promise构建的异步模式上,为了简化异步流程控制,有一些新模式:
- Promise.all([p1,p2]):等待多个并行/并发的任务都完成,只要有一个拒绝,就会立即被拒绝
- Promise.race([p1.p2]):任何一个 Promise 决议为完成,就会完成,任何一个决议为拒绝,就会拒绝,
- 立即数会立马决议
- 空数组永远不会决议
- Promise.none([p1,p2]):类似 all,不过完成和拒绝的情况互换,全部的 Promise 都被拒绝,才会拒绝,
- Promise.any([p1,p2]):类似 all,但会忽略拒绝,如果有一个完成,就会返回完成的 promise
- Promise.first([p1,p2]):类似 any,只要第一个promise完成,就会忽略其他的
- Promise.last([p1,p2]):类似 first,但要最后一个完成才可以
Promise API
构造器 Promise 使用方法:
- 和 new 一起使用
- 必须提供一个函数回调,这个函数回调是同步或立即调用的。
- 回调函数接受两个参数
- reject(): 拒绝这个 Promise
- resolve() :有可能是拒绝,也可能是完成
- 如果传的值是非Promise、非thenable的立即值,promise就会用这个值完成
- 如果传的值是一个真正的Promise或thenable,这个值会被递归展开,并且promise将取用其最终决议值或状态
then
Promise中是通过then()方法来指定处理异步操作结果的方法,为Promise实例添加状态改变时的回调函数。
then方法会返回一个新的 Promise 实例。在链式中的 then 方法(第二个开始),它们的 resolve 中的参数是前一个 then() 中 resolve语句的返回值。
catch()错误处理
catch()用于指定发生错误时的回调
Promise 的 Error 具有冒泡性质,会一直向后传递,直到被捕获为止。
catch 就是.then(null, rejection)的别名,所以,catch 也会返回一个 Promise 对象
- 当出错时,会先处理之前的错误,再通过 return,将值继续传递给后面的 then 方法中。
- 若没出错,会跳过 catch
resolve()
Promise.reslolve 参数的几种情况:
- 是 thenable 对象。转换成 Promise 对象,然后立即执行 thenable 对象中的 then方法,
- 立即值/不带 thenable 的对象,会返回一个新的 Promise 对象,状态是 resolve,然后将参数传给 resolve
- 不带任何参数:会返回一个 resolve 状态的 Promise 对象
reject()
Promise.reject() 返回新的 Promise 对象,该实例的状态是 rejected。
实用方法
- done:回调链中,若最后一个抛出错误,因为 promise 内部的错误不会冒泡到全局,所以会捕获不到,所以一般用done处理回调链的尾端
- finally:不管promise什么状态都会执行的操作。
promise的缺点
promise 对异步操作进行封装,使异步操作以同步的流程表达出来。但他同时也存在一些问题:
-
无法取消
-
内部错误不会反应到外部
-
当处于未完成状态时,无法得知进展到哪一个阶段
好题分享
console.log('1');
setTimeout(function() {
console.log('2');
process.nextTick(function() {
console.log('3');
})
new Promise(function(resolve) {
console.log('4');
resolve();
}).then(function() {
console.log('5')
})
})
process.nextTick(function() {
console.log('6');
})
new Promise(function(resolve) {
console.log('7');
resolve();
}).then(function() {
console.log('8')
})
setTimeout(function() {
console.log('9');
process.nextTick(function() {
console.log('10');
})
new Promise(function(resolve) {
console.log('11');
resolve();
}).then(function() {
console.log('12')
})
})
结果是:1 7 6 8 2 4 3 5 9 11 10 12