前言
众所周知,目前主流的JS环境都是以单线程模式去执行的JavaScript代码,之所以采用单线程模式工作,与它最早的设计初衷有关.最早JS就是一门运行在浏览器端的脚本语言,目的是为了实现页面上的动态交互,而实现动态交互的核心就是DOM操作,这就决定了必须使用单线程,不然会出现很复杂的线程同步问题
同步模式
同步模式指的是代码中的任务依次执行,后一个任务必须要等待前一个任务结束才能开始执行,程序的执行顺序跟代码的编写顺序完全一致.
异步模式
不会等待这个任务的结束才开始下一个任务,对于耗时操作,都是开启过后立即往后执行下一个任务,耗时任务的后续逻辑一般通过回调函数的方式去定义
Promise
不多逼逼,直接上案例
function ajax (url) {
return new Promise ((resolve, reject) => {
let xhr = new XMLHttpRequest()
xhr.open('GET', url)
xhr.responseType = 'json'
xhr.onload =function () {
if (this.staths === 200) {
resolve(this.response)
}else{
reject(new Error(this.statusText))
}
}
xhr.send()
})
}
链式调用
- Promise 对象的 then 方法会返回一个全新的 Promise 对象
- 后面的 then 方法就是在为上一个 then 返回的 Promise 注册回调
- 前面 then 方法中回调函数的返回值会作为后面 then 方法回调的参数
- 如果回调中返回的是 Promise,那后面 then 方法的回调会等待它的结束
静态方法
Promise.resolve()
- 快速的把一个值转化为 Promise 对象;
- 如果接收的参数是另一个 Promise 对象,那这个 Promise 对象会被原样返回
- 如果接收的参数是一个对象,且这个对象有一个跟 Promise 对象一样的 then 方法,即 then 方法里有一个 onFulfilled 跟 onRejected 方法,那这个对象也可以作为 Promise 对象来执行.带有这个 then 方法的对象,实际上是实现了一个 thenable 的接口,即是一个可以被 then 的对象
Promise.reject() - 快速创建一个一定是失败的 Promise 对象
并行执行
Promise.all()
接收一个数组,数组里每个元素都是 Promise(P1) 对象,然后返回一个全新的 Promise(P2) 对象,当内部所有的 Promise(P1) 对象都完成之后,返回的 Promise(P2) 对象才会完成.此时这个 Promise(P2) 拿到的结果是个数组,数组里面包含着每个异步任务执行的结果.当且仅当所有的任务都执行成功时 Promise(P2) 才会成功结束,任何一个任务失败都会导致 Promise(P2) 失败
Promise.race()
与 all() 大致相同,不同的是 all() 会等待所有组合的 Promise 都结束而且是成功结束才会成功完成,而 race() 只会等待第一个结束的任务
执行时序
Event Loop 中,每一次循环称为 tick,每一次 tick 的任务如下:
- 执行栈选择最先进入队列的宏任务(一般都是 script ),执行其同步代码直至结束
- 检查是否存在微任务,有则会执行至微任务队列为空
- 如果宿主为浏览器,可能会渲染页面
- 开始下一轮tick,执行宏任务中的异步代码( setTimeout 等回调)
比较值得注意的是 Promise 既包含宏任务,也包含微任务,Promise 内部执行为宏任务,then 回调为微任务
generator
避免异步编程中回调嵌套过深,提供更好的异步编程解决方案
- Generator(生成器):是一类特殊的函数,跟普通函数声明时的区别是加了一个*号
- Iterator(迭代器):当我们实例化一个生成器函数之后,这个实例就是一个迭代器.可以通过 next() 方法去启动生成器以及控制生成器的是否往下执行
- yield/next:通过 yield 语句可以在生成器函数内部暂停代码的执行使其挂起,此时生成器函数仍然是运行并且是活跃的,其内部资源都会保留下来,只不过是处在暂停状态
在迭代器上调用 next() 方法可以使代码从暂停的位置开始继续往下执行
// 首先声明一个生成器函数
function *main() {
console.log('starting *main()');
yield; // 打住,不许往下走了
console.log('continue yield 1');
yield; // 打住,又不许往下走了
console.log('continue yield 2');
}
// 构造处一个迭代器it
let it = main();
// 调用next()启动*main生成器,表示从当前位置开始运行,停在下一个yield处
it.next(); // 输出 starting *main()
// 继续往下走
it.next(); // 输出 continue yield 1
// 再继续往下走
it.next(); // 输出 continue yield 2
async&await
async & await 是 generator 的语法糖,简单来说,async 是通过 Promise 包装异步任务的
async function async1() {
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2 end')
}
async1()
改成 Promise 的写法就是
new Promise((resolve, reject) => {
async2()
}).then(() => {
// 执行async1()函数await之后的语句
console.log('async1 end')
})
当调用 async1 函数时,会马上输出 async2 end,并且函数返回一个 Promise,接下来在遇到 await的时候会就让出线程开始执行 async1 外的代码(可以把 await 看成是让出线程的标志)
然后当同步代码全部执行完毕以后,就会去执行所有的异步代码,那么又会回到 await 的位置,去执行 then 中的回调