1、前端开发中的同步异步编程
知识点
- 进程与线程 & 浏览器中常用的线程
- GUI渲染线程
- JS引擎线程
- 事件触发线程
- 定时触发器线程
- 异步HTTP请求线程
- WebWorker
- ....
- JS是单线程异步编程
- EventLoop
- EventQueue
- WebAPIS
- 宏任务 macrotask [ˈmækroʊ]
- 微任务 microtask [ˈmaɪkroʊ]
进程:一个程序
线程:这个程序中具体做事情的
同时做多件事情,需要多个线程;只有一个线程,一次只能做一件事,这件事干完才可以干下一个事情
进程中包含多个线程
同步编程:一次只能做一件事情,当前事件完成才能做下一个事情
异步编程:利用多线程机制,同时可以做多件事情
单线程如何实现异步编程?
浏览器 --- JS
浏览器打开一个页面,就会开辟一个进程,浏览器是多线程的;但他只分配了一个线程:JS引擎线程,用来渲染和解析js,所以js是单线程的;js中大部分代码都是同步编程
JS代码的执行是单线程的:因为浏览器只分配一个线程“JS引擎线程(主线程)”用来渲染和解析JS
- JS中大部分代码都是同步的:如果此时主线程正在执行某些代码,那么其余的事情都做不了(例如:循环就是同步代码,如果JS中出现死循环,那么主线程永远空闲不下来,其他的事情都处理不了,整个页面就卡在这卡死了!!!)
- JS中也有一部分代码是异步的:并不是像想象中的同时执行很多代码,而是“基于EventLoop&浏览器的多线程机制”实现出监听、排队的机制
为什么js是单线程
js作为主要运行在浏览器的脚本语言,js主要用途之一是操作DOM。
在js高程中举过一个例子,如果js同时有两个线程,同时对同一个dom进行操作,这时浏览器应该听哪个线程的,如何判断优先级?
为了避免这种问题,js必须是一门单线程语言,并且在未来这个特点也不会改变。
| 宏任务 macrotask | 微任务 microtask:优先级高 |
|---|---|
| setTimeout/setInterval | requestAnimationFrame「有争议」实现JS动画 |
| 事件绑定/队列 | Promise.then/catch/finally |
| XMLHttpRequest/Fetch | async/await |
| setImmediate「Node」 | queueMicrotask 创建一个新的异步微任务 |
| MessageChannel | process.nextTick「Node」 |
| script整体代码块 | MutationObserver 监听DOM元素属性改变 |
| IntersectionObserver 监听DOM元素和视口交叉的信息 |
script整体代码块是宏任务【同步的宏任务】
requestAnimationFrame
window.requestAnimationFrame()告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行MutationObserver
MutationObserver接口提供了监视对DOM树所做更改的能力。它被设计为旧的Mutation Events功能的替代品,该功能是DOM3 Events规范的一部分。IntersectionObserver
**IntersectionObserver**接口 (从属于Intersection Observer API) 提供了一种异步观察目标元素与其祖先元素或顶级文档视窗(viewport)交叉状态的方法。祖先元素与视窗(viewport)被称为根(root)。当一个
IntersectionObserver对象被创建时,其被配置为监听根中一段给定比例的可见区域。一旦IntersectionObserver被创建,则无法更改其配置,所以一个给定的观察者对象只能用来监听可见区域的特定变化值;然而,你可以在同一个观察者对象中配置监听多个目标元素。重绘&重排/回流
当DOM的变化引发了元素几何属性的变化,比如
改变元素的宽高,元素的位置,导致浏览器不得不重新计算元素的几何属性,并重新构建渲染树,这个过程称为“重排”。完成重排后,要将重新构建的渲染树渲染到屏幕上,这个过程就是“重绘”。简单的说,重排负责元素的几何属性更新,重绘负责元素的样式更新。而且,重排必然带来重绘,但是重绘未必带来重排。比如,
改变某个元素的背景,这个就不涉及元素的几何属性,所以只发生重绘。
随堂练习题
setTimeout(() => { console.log(1); }, 20); console.log(2); setTimeout(() => { console.log(3); }, 10); console.log(4); console.time('AA'); for (let i = 0; i < 90000000; i++) { // do soming } console.timeEnd('AA'); //=>AA: 79ms 左右 console.log(5); setTimeout(() => { console.log(6); }, 8); console.log(7); setTimeout(() => { console.log(8); }, 15); console.log(9);
2、Promise解析
基于回调函数的方式封装ajax请求
ajax串行:多个ajax请求间存在依赖,只有上一个请求成功,才能发送下一个请求(例如:第二个请求的发送,需要依赖于第一个请求的结果)
ajax并行:请求之间没有啥依赖,可以同时发送多个请求「一般会额外处理一些事情:等待所有请求都成功,统一干啥事」
在没有promise之前,基于回调函数的方式管理ajax请求,在串行的需求中,很容易产生“回调地狱”
$.ajax({ url: '/api/test1', method: 'GET', dataType: 'json', success(value) { console.log('第一个请求结果:', value); $.ajax({ url: '/api/test2', success(value) { console.log('第二个请求结果:', value); $.ajax({ url: '/api/test3', success(value) { console.log('第三个请求结果:', value); } }); } }); } });而promise是ES6中专门用来管理异步编程的,基于它可以避免回调地狱的问题
const http1 = () => { return new Promise(resolve => { $.ajax({ url: '/api/test1', success: resolve }); }); }; const http2 = () => { return new Promise(resolve => { $.ajax({ url: '/api/test2', success: resolve }); }); }; const http3 = () => { return new Promise(resolve => { $.ajax({ url: '/api/test3', success: resolve }); }); }; http1() .then(value => { console.log('第一个请求结果:', value); return http2(); }) .then(value => { console.log('第二个请求结果:', value); return http3(); }) .then(value => { console.log('第三个请求结果:', value); }); //语法糖 (async function () { let value = await http1(); console.log('第一个请求结果:', value); value = await http2(); console.log('第二个请求结果:', value); value = await http3(); console.log('第三个请求结果:', value); })();
Promise基础
Promise是ES6新增的内置类:
- 不兼容IE浏览器「EDGE可以兼容的」:真实项目开发的时候,如果使用了promise,还要兼容IE浏览器,需要处理兼容
@1 babel/preset语法包,无法编译Promise内置类,它只能编译类似于let/const这种常规语法
@2 我们需要基于 @babel/polyfill 实现Promise的兼容处理「原理:基于ES5手动实现一个Promise」
- 使用的时候是创建类的实例:实例具备私有属性、可以使用Promise.prototype上的方法、Promie作为对象存在一些静态的私有属性方法
let p1 = new Promise([executor])[executor]
- 必须是一个函数,传递的不是函数则会报错 Uncaught TypeError: Promise resolver xxx is not a function
- new Promise的时候,会立即把[executor]函数执行「这步操作是同步编程」
- [executor]函数执行的时候,会接受两个实参值,我们分别用resolve/reject形参来存储
- resolve/reject存储的值也是函数
- resolve([value]):把创建实例的状态改为成功态(fulfilled),实例的值是[value](成功的结果)
- reject([reason]):把创建实例的状态改为失败态(rejected),实例的值是[reason](失败原因)
- 如果[executor]函数执行过程中,有异常错误抛出,则实例的状态依然是rejected,实例的值是报错原因
- 一但状态被改为fulfilled或者rejected,则不能再次修改了
实例p1具备的私有属性
- [[PromiseState]]:"pending" 实例状态:pending(准备/默认状态) & fulfilled(成功态) & rejected(失败态)
- [[PromiseResult]]:undefined 实例的值(结果):存储成功的结果或者失败的原因,状态为pending则默认值是undefined
- new Promise的时候会立即执行executor函数 同步
let p1 = new Promise(function executor(resolve,reject){ resolve(100);//立即修改实例的状态和值 reject(100); })new Promise产生的实例,状态是成功还是失败,是由executor执行是否报错{执行报错,则实例是失败态}、以及resolve还是reject执行决定
Promise.resolve(100):直接创建状态是成功的实例
Promise.reject(100):直接创建状态是失败的实例
Promise.prototype 为实例提供的公共属性方法
- catch
- finally
- Symbol(Symbol.toStringTag): "Promise"
- then
实例.then(onfulfilled,onrejected)
@1 首先观察实例的状态,如果此时已经知道实例是成功或者失败的状态
- 创建一个异步“微任务”【放在WebAPI中去监听,监听的时候知道它的状态,所以直接把执行的哪个方法,挪至到EventQueue中排队等着】
- 状态是成功的,后期执行的是onfulfilled
- 状态是失败的,后期执行的是onrejected
console.log(1); let p1 = new Promise(function executor(resolve, reject) { console.log(2); resolve(100); //立即修改实例的状态和值 // reject(0); console.log(3); }); console.log(4); p1.then(function onfulfilled(value) { console.log('成功:', value); }, function onrejected(reason) { console.log('失败:', reason); }); console.log(5); // 答案:1 2 3 4 5 成功:100@2 如果实例此时的状态还是pending
- 把onfulfilled/onrejected先存储到指定的容器中【放在WebAPI中监听状态的改变】
- 当后期执行resolve/reject的时候
- 立即修改实例的状态和值【同步】
- 创建一个异步微任务,后面让指定容器中的方法执行【挪至EventQueue中排队等待】
console.log(1); let p1 = new Promise(resolve => { console.log(2); setTimeout(() => { console.log(3); resolve(100); console.log(4); }, 1000); console.log(5); }); console.log(6); p1.then(value => { console.log('成功:', value); }, reason => { console.log('失败:', reason); }); console.log(7); // 答案:1 2 5 6 7 ...过1S后... 3 4 成功:100@1 已经明确知道promise的状态是成功还是失败的了,它也不会立即执行onfulfilled/onrejected,而是创建一个“异步的微任务”「WebAPI -> EventQueue -> 同步执行完,去EventQueue中查找执行」
@2 此时的promise的状态还是pending,则先把onfulfilled/onrejected存储起来「也可以理解为放在WebAPI中监听」;后续当我们基于resolve/reject把实例的状态“立即”修改后,也并不会立即去通知onfulfilled/onrejected执行,而是创建一个“异步微任务”「也可以理解为把WebAPI中的任务挪至到EventQueue中排队去等待了」;
“实例.then”会返回一个全新的promise实例【p2】,这个实例的成功失败,有p1管控的onfulfilled或者onrejected不论哪个方法执行决定
- 方法执行是否返回新的promise实例,如果没有返回:
- 方法执行只要不报错,则p2就是成功的,值就是函数返回值
- 执行报错,则p2就是失败的,值是报错原因
- 如果返回的是新的promise实例【new-promise】,则new-promise的状态和值决定了p2的状态和值
let p1 = Promise.resolve(100); let p2 = p1.then(value => { console.log('成功:', value); return Promise.reject(value * 10); }, reason => { console.log('失败:', reason); return reason / 10; }); // 因为p1管理的onfulfilled方法还在EventQueue中排队等待执行,所以此时p2的状态还是pending // + 把onfulfilled/onrejected存储起来,放在WebAPI监听,监听p2状态的改变「把p1管控的onfulfilled执行就知道了」 // + 当p1对应的onfulfilled执行后,把p2变为失败,此时把p2对应的onrejected挪至到EventQueue中排队等着执行... p2.then(value => { console.log('成功:', value); }, reason => { console.log('失败:', reason); }); //------------------------------------------ let p1 = new Promise((resolve, reject) => { setTimeout(() => { reject(100); }, 1000); }); p1.then(value => { console.log('成功:', value); }).catch(reason => { //.catch === .then(null,onrejected) console.log('失败:', reason); });**“THEN链的穿透和顺延机制”:**执行then,如果onfulfilled/onrejected没有传递,则顺延到下一个同等状态的方法上
promise实例.catch([onrejected]) : 等同于 promise实例.then(null,[onrejected])
- 如果实例状态是失败,而我们也没有去设置onrejected进行处理,那么控制台会“抛异常”,但是不影响其余的代码执行!!
- 所有我们会在THEN链的最末尾设置一个catch,这样不论哪一级返回失败的promise实例,都会把catch中的onrejected执行
Promise.reject(100).then(value => { console.log('成功:', value); return value / 10; }).then(value => { console.log('成功:', value); return value / 10; }).catch(reason => { console.log('失败:', reason); }).finally(()=>{ });Promise 作为普通对象,具备静态私有属性方法
- all
- allSettled
- any
- race
- reject:Promise.reject(xxx) 创建一个状态为rejected、值是xxx的promise实例
- resolve:Promise.resolve(xxx) 创建一个状态为fulfilled、值是xxx的promise实例
- ...
Promise.all/any/race([promises])
- [promises]是包含零到多个promise实例的集合,如果其中某一项不是promise实例,则浏览器也会默认把其变为状态是fulfilled、值是自身的实例;返回结果都是一个新的promise实例「例如:@P」!!
- all:[promises]集合中每一项都是成功的,@P就是成功的,值是按照原始集合顺序,依次存储每一个实例的值;只要有一项是失败的,则@P就是失败的,值是此失败项的原因,后续还没处理的实例也不用再处理了!!
- any:集合中是要有一项是成功的,则@P就是成功的,值是成功项的值;所有项都是失败的,@P才是失败的!!
- race:看集合中谁最先处理完成,那么就按照他的结果作为@P的结果,哪怕是失败态!!
let p1 = new Promise(resolve => { setTimeout(() => { resolve(1); }, 2000); }); let p2 = Promise.reject(2); let p3 = 3; //-> Promise.resolve(3) Promise.race([p1, p2, p3]).then(value => { console.log('成功:', value); }).catch(reason => { console.log('失败:', reason); });
async/await:是promise+genreatar的语法糖
- async修饰的函数,默认返回值会变为一个promise实例
- 必须在函数中使用await,而且当前函数需要经过async修饰
- async:用来修饰函数
- 让函数返回的结果是一个promise实例
- 如果在函数中使用await,则函数必须经过async的修饰
- await:可以把异步操作改为类似于同步的效果
- await后面必须跟一个promise实例,如果不是也会变为一个实例(状态是fulfilled、值是本身)
- 需要等待后面的promise实例状态为成功,才会执行”当前上下文“await下面的代码;如果后面实例是失败的,则下面代码就不执行了!
- await后面的实例是失败的,我们可以基于try/catch实现异常捕获,在catch中处理状态是失败要做的事情
async function fn() {
return 1;
}
console.log(fn()); //promise实例: 状态fulfilled 值1
async function fn() {
console.log(num);
return 1;
}
console.log(fn()); //状态rejected 值是报错原因
async function fn() {
return Promise.reject('NO');
}
console.log(fn()); //返回值本身是promise实例,则以自己返回为主
//--------------------------------------------------------------
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
reject(100);
}, 1000);
});
(async function () {
// await 后面放置的是promise实例「如果不是,则默认变为状态是成功,值是这个值的promise实例」
// + 把当前上下文中,await“下面的代码”作为一个“异步微任务”,放在WebAPI中去监听,监听实例的状态
// + 如果状态是成功,把其“下面的代码执行”挪至到EventQueue中排队等着
// + 如果状态是失败,则下面代码不会执行
let value = await p1;
console.log('成功:', value);
})();
(async function () {
// 捕获await后面实例是失败的?
try {
let value = await p1;
console.log('成功:', value);
} catch (err) {
console.log('失败:', err);
}
})();
3、随堂练习题 & 面试题
async function async1() { console.log('async1 start'); await async2(); console.log('async1 end'); } async function async2() { console.log('async2'); } console.log('script start'); setTimeout(function() { console.log('setTimeout'); }, 0) async1(); new Promise(function(resolve) { console.log('promise1'); resolve(); }).then(function() { console.log('promise2'); }); console.log('script end');
let body = document.body; body.addEventListener('click', function () { Promise.resolve().then(() => { console.log(1); }); console.log(2); }); body.addEventListener('click', function () { Promise.resolve().then(() => { console.log(3); }); console.log(4); });
console.log('start'); let intervalId; Promise.resolve().then(() => { console.log('p1'); }).then(() => { console.log('p2'); }); setTimeout(() => { Promise.resolve().then(() => { console.log('p3'); }).then(() => { console.log('p4'); }); intervalId = setInterval(() => { console.log('interval'); }, 3000); console.log('timeout1'); }, 0);
setTimeout(() => { console.log('a'); }); Promise.resolve().then(() => { console.log('b'); }).then(() => { return Promise.resolve('c').then(data => { setTimeout(() => { console.log('d') }); console.log('f'); return data; }); }).then(data => { console.log(data); });
function func1() { console.log('func1 start'); return new Promise(resolve => { resolve('OK'); }); } function func2() { console.log('func2 start'); return new Promise(resolve => { setTimeout(() => { resolve('OK'); }, 10); }); } console.log(1); setTimeout(async () => { console.log(2); await func1(); console.log(3); }, 20); for (let i = 0; i < 90000000; i++) {} //循环大约要进行80MS左右 console.log(4); func1().then(result => { console.log(5); }); func2().then(result => { console.log(6); }); setTimeout(() => { console.log(7); }, 0); console.log(8);
4、Promise源码解析
new.target 检测是否为new执行
- 是new执行,获取的是构造函数
- 如果不是,则为undefined
queueMicrotask 创建一个异步微任务
queueMicrotask(function () { onfulfilled(self.result); });Promise.all()中必须是一个迭代器,如果不是会报错;
Set,NodeList,HTMLCollection中都有Symbol.toStringTag 这个属性;
如果为Set集合,获取长度使用size
(function() { /* 核心部分 */ function Promise(executor) { var self = this, change; // 保证executor是个函数 & 是基于new Promise执行的 if (typeof executor !== "function") throw new TypeError("Promise resolver is not a function"); if (!(self instanceof Promise)) throw new TypeError("undefined is not a promise"); // 实例设置状态和值 self.state = "pending"; self.result = undefined; self.onfulfilledCallbacks = []; self.onrejectedCallbacks = []; change = function change(state, result) { if (self.state !== "pending") return; self.state = state; self.result = result; // 异步通知事先存储的指定方法执行 setTimeout(function() { var callbacks = state === 'fulfilled' ? self.onfulfilledCallbacks : self.onrejectedCallbacks; callbacks.forEach(function(callback) { if (typeof callback !== "function") return; callback(self.result); }) }); } // 立即执行executor,并且监听是否报错 try { executor(function resolve(value) { change('fulfilled', value); }, function reject(reason) { change('rejected', reason); }) } catch (err) { change('rejected', err); } } /* 原型部分 */ var resolvePromise = function resolvePromise(promise, x, resolve, reject) { // 根据onfulfilled/onrejected执行的返回结果x,去验证.then返回的实例promise是成功还是失败「基于执行resolve/reject」 if (promise === x) throw new TypeError("Chaining cycle detected for promise #<Promise>"); if (x !== null && /^(object|function)$/.test(typeof x)) { var then; // 避免x.then因做了数据劫持,导致访问可能会报错,所以做个特殊处理 try { then = x.then; } catch (err) { reject(err); } if (typeof then === "function") { //有一个程序正在处理,其他程序就不处理 var called = false; // 说明x是一个新的promise实例:x的成功/失败,决定了promise的成功或者失败 try { then.call( x, function onfulfilled(y) { if (called) return; called = true; resolvePromise(promise, y, resolve, reject); }, function onrejected(r) { if (called) return; called = true; reject(r) }) } catch (err) { if (called) return; called = true; reject(err); } } } // 说明onfulfilled/onrejected方法执行返回的结果x并不是一个实例:让promise状态是fulfilled、值就是函数返回值x resolve(x); } Promise.prototype = { constructor: Promise, then: function(onfulfilled, onrejected) { var self = this, promise; if (!(self instanceof Promise)) throw new TypeError("Method Promise.prototype.then called on incompatible receiver #<Promise>"); // 实现THEN链的穿透机制 if (typeof onfulfilled !== "function") { onfulfilled = function onfulfilled(value) { return value; } } if (typeof onrejected !== "function") { onrejected = function onrejected(reason) { throw reason; } } // 每一次执行TEHN会返回一个全新的实例 promise = new Promise(function(resolve, reject) { // 验证执行THEN的时候,实例的状态是否已知 // + 已知状态:创建异步微任务,执行onfulfilled/onrejected中的某个方法 // + 未知状态:把onfulfilled/onrejected存储到指定的容器中,当实例状态发生改变,再通知指定方法执行「异步」 switch (self.state) { case "fulfilled": setTimeout(function() { try { var x = onfulfilled(self.result); resolvePromise(promise, x, resolve, reject); } catch (err) { reject(err); } }); break; case "rejected": setTimeout(function() { try { var x = onrejected(self.result); resolvePromise(promise, x, resolve, reject); } catch (err) { reject(err); } }); break; default: self.onfulfilledCallbacks.push(function(value) { try { var x = onfulfilled(value); resolvePromise(promise, x, resolve, reject); } catch (err) { reject(err); } }); self.onrejectedCallbacks.push(function(reason) { try { var x = onrejected(reason); resolvePromise(promise, x, resolve, reject); } catch (err) { reject(err); } }); } }) }, catch: function myCatch(onrejected) { //this.then(null,onrejected); 如果this如果不是promise实例,没有then方法会报错 return Promise.prototype.then.call(this, null, onrejected); } }; /* 为Promise原型添加[Symbol.toStringTag]属性 */ if (typeof Symbol !== "undefined") Promise.prototype[Symbol.toStringTag] = "Promise"; /* 静态属性 */ var isPromise = function isPromise(x) { if (x !== null && /^(object|function)$/.test(typeof x)) { var then; try { then = x.then; } catch (_) { return false; } if (typeof then === "function") return true; } return false; } var checkIterator = function checkIterator(arr) { if (arr == null) return false; if (typeof Symbol !== "undefined") { var iterator = arr[Symbol.iterator]; return iterator ? true : false; } return Array.isArray(arr); } // 以实例为准 Promise.resolve = function resolve(value) { if (isPromise(value)) return value; return new Promise(function(resolve) { resolve(value); }) } Promise.reject = function reject(reason) { return new Promise(function(_, reject) { reject(reason); }) } Promise.all = function all(promises) { if (!checkIterator(promises)) throw TypeError("promises is not iterator"); var n = 0, results = [], len = promises.length || promises.size; return new Promise(function(resolve, reject) { for (var i = 0; i++; i < len) { (function(i) { var promise = promises[i]; if (!isPromise(promise)) promise = Promise.resolve(promise); promise.then(function onfulfilled(value) { // 某一项成功,则把当前成功项的值记录到result中(索引和promise中的索引保持一致) results[i] = value; // 都成功,最后整体返回成功 n++; if (n >= len) resolve(results); }, function onrejected(reason) { // 只要有一项是失败的,则整体返回失败,值是当前失败项的值 reject(reason); }) })(i); } }) } Promise.any = function any(promises) { if (!checkIterator(promises)) throw TypeError("promises is not iterator"); var n = 0, len = promises.length || promises.size; return new Promise(function(resolve, reject) { for (var i = 0; i++; i < len) { (function(i) { var promise = promises[i]; if (!isPromise(promise)) promise = Promise.resolve(promise); promise.then(function onfulfilled(value) { resolve(value); }, function onrejected(reason) { n++; if (n >= len) reject('All promises were rejected') }) })(i); } }) } /* 暴露API */ if (typeof module !== "object" && module.exports !== "object") { module.exports = Promise } if (window !== "undefined") window.Promise = Promise; })(); var p1 = new Promise(function(resolve, reject) { resolve(100); }) p1.then(function(value) { }, function(reason) { })字节跳动面试题:当失败的等于指定的值,再输出状态为onrejected,结果为失败的值
// 字节跳动面试题:当失败的等于指定的值,再输出状态为onrejected,结果为失败的值 Promise.all = function all(promises, limit) { if (!checkIterator(promises)) throw new TypeError("promises is not iterable"); if (typeof limit !== "number" || isNaN(limit)) limit = 1; var n = 0, m = 0, results = [], reasons = [], len = promises.length || promises.size; limit = limit < 1 ? 1 : (limit > len ? len : limit); return new Promise(function(resolve, reject) { for (var i = 0; i < len; i++) { (function(i) { var promise = promises[i]; if (!isPromise(promise)) promise = Promise.resolve(promise); promise.then(function resolve(value) { n++; results[i] = value; if (n >= len) resolve(results); }, function reject(reason) { m++; reasons[i] = reason; if (m >= limit) { reject(limit === 1 ? reason : reasons); return; } n++; results[i] = null; }) })(i); } }) }