同步和异步的区别是什么?
为什么不能直接用同步,异步是怎么回事,异步怎么用,为什么会有异步
同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有等主线程任务执行完毕,"任务队列"开始通知主线程,请求执行任务,该任务才会进入主线程执行。
基于JS是单线程语言
使用异步是为了不阻塞代码运行
手写用Promise加载一张图片?
前端使用异步的场景?
等待的场景
定时器场景题
单线程和异步
JS是单线程语言,只能同时做一件事儿,页面会卡住
浏览器和nodejs已经支持JS启动进程,如Web Worker
JS和DOM渲染公用一个进程,因为JS可修改DOM结构
为了避免卡住需要用异步解决单线程等待,基于callback函数形式
应用场景(为什么必须要用异步)
网络请求,加载图片
定时任务
callback hell和Promise (为什么会出现)
嵌套,异步调异步,用Promise变成单层的
在then里面return手动抛出Promise
event loop
promise进阶
async/await
微任务/宏任务
请描述事件轮询的机制,可画图
跟异步的关系
什么是宏任务和微任务,两者有什么区别
Promise的状态,如何变化?
场景题 promise then和catch的连接
场景题 async/await语法
场景题 promise和setTimeout顺序
外加async/await的顺序问题
event loop
JS是单线程,异步要基于回调来实现,异步回调基于事件循环实现
JS从前到后一行行执行,某一行报错停止下面代码执行
先把同步执行完,再执行异步(如何实现...回调...)
Call Stack 调用栈
Web APIs 定时器等 DOM/BOM ajax 浏览器API
Event Loop 一个循环
Callback Quene 回调函数队列
1. 第一行代码加入调用栈底部,调用栈执行然后弹出
2. 定时器代码进入调用栈(记录下,等待时机),在Web APIs中设置定时器和参数,时间到了以后定时器把参数函数放入Callback Queue(时机到了,进入callback queue),执行后弹出
3. 第三行代码进入调用栈底部,调用栈执行然后弹出
4. 同步代码全部执行完,调用栈空,**事件循环把函数放入调用栈,立即停止工作?**,无限循环是指在主线程空闲时.
5. 浏览器启动event loop机制,不停地循环从Callback Queue中取出放到Call Stack,继续轮询查找
6. 当定时器触发参数函数进入callback Queue 被事件循环取出放入调用栈,调用栈把函数体取出放入栈顶,执行完毕弹出.
当一个异步事件返回结果后,js会将这个事件加入与当前执行栈不同的另一个队列,我们称之为事件队列。被放入事件队列不会立刻执行其回调,而是等待当前执行栈中的所有任务都执行完毕, 主线程处于闲置状态时,主线程会去查找事件队列是否有任务。如果有,那么主线程会从中取出排在第一位的事件,并把这个事件对应的回调放入执行栈中,然后执行其中的同步代码...,如此反复,这样就形成了一个无限的循环。这就是这个过程被称为"事件循环(Event Loop)"的原因。
DOM事件和event loop
事件触发时回调函数被放入Callback Quene
人工合成(synthetic)的事件派发(dispatch)是同步执行的,包括执行click()和dispatchEvent()这两种方式。只有浏览器自己触发的事件才是放在一个 task 里执行的。
DOM事件基于事件循环实现但是不是异步
Promise
三种状态
pending—> 不可逆 fulfilled rejected
state — 最初是 "pending",然后在 resolve 被调用时变为 "fulfilled",或者在 reject 被调用时变为 "rejected"
resolved 放在异步里面,浏览器打印时未执行仍是pending,但是点开会发现已经执行,delay取默认值0
rejected
状态的表现和变化
pending状态不会触发then和catch
then和catch对状态的影响
当发生一个常规的错误(error)并且未被 try..catch 捕获时会发生什么?脚本死了,并在控制台(console)中留下了一个信息。对于在 promise 中未被处理的 rejection,也会发生类似的事儿。
then和catch正常返回resolved,里面有报错则返回rejected,就是都还可以往下传递
API
Promise.all 接受一个 promise 数组作为参数(从技术上讲,它可以是任何可迭代的,但通常是一个数组)并返回一个新的 promise。结果数组中元素的顺序与其在源 promise 中的顺序相同。
不是 promise,那么它将被"按原样"传递给结果数组。
如果任意一个 promise 被 reject,由 Promise.all 返回的 promise 就会立即 reject,并且带有的就是这个 error。
Promise.race
只等待第一个 settled 的 promise 并获取其结果(或 error)。
async/await
返回fullfilled的Promise(如果有Error,返回rejected的Promise),await相当于then并不改变Promise状态
异步callback hell的风险
Promise基于回调函数,但是实现了链式逻辑避免了嵌套
async/await是同步语法,消灭异步的回调函数,用同步语法写异步代码,写起来更方便
**和Promise的关系**
和Promise并不互斥,应该结合起来用
async函数返回的是Promise对象
let p = (async function(){})();
undefined
p
Promise {<fulfilled>: undefined}
!!!里面什么都不干也返回Promise
await相当于then,遇到error会throw,没有catch,async会返回rejected的Promise
普通值相当于 await Promise.resolve(data)
await 实际上会暂停函数的执行,主线程同步等待直到 promise 状态变为 settled,然后以 promise 的结果继续执行。这个行为不会耗费任何 CPU 资源,因为 JavaScript 引擎可以同时处理其他任务:执行其他脚本,处理事件等。
await一个rejected的Promise不会执行,如果没有catch就报错
(async function(){
console.log('b',b);
const c = await Promise.reject(300)
console.log('c',c)
})()
!!!! await后面的以及下面几行都不会执行,因为报错了
try...catch代替Promise的catch
异步的本质
JS还是单线程,异步需要回调,还是基于event loop实现
for...of用于异步遍历
for...in(forEach,for)是常规的同步遍历
循环里面使用await时有区别
保持异步的顺序
宏任务macro 微任务micro
什么是宏任务和微任务
宏任务:script(整体代码)、setTimeout,setInterval,Ajax,DOM事件、setImmediate、I/O、UI rendering
微任务:Promise的回调,async/await、Object observe、Mutation Observe
任务优先级:process.nextTick > promise.then > setTimeout > setImmediate
每一个宏任务带着几个微任务一个个去执行。
event loop 和 DOM渲染
JS是单线程的,和DOM渲染共用一个线程,JS执行的时候需要留一些时机供DOM渲染
Call Stack空闲(一次轮询结束)→尝试DOM渲染→触发Event Loop(进入下一轮轮询)
alert阻断js执行和DOM渲染,DOM结构已更新(js可以取到更新的DOM元素)但是尚未渲染
宏任务在DOM渲染后触发,微任务在DOM渲染前触发(为什么?)
setTimeout在调用栈中执行时把callback放入定时器,定时器时间到了把callback放进Callback Queue由事件循环取出放进Call Stack,因为Event Loop在DOM渲染之后进行.
Promise不是W3C规范(浏览器规定)是ES规范,不会进入Web APIs,而是等待时机把callback放进micro task queue中,call stack清空之后会先执行当前微任务(在循环调用微任务时,就会阻塞页面的渲染)多个微任务执行,会在当前宏任务阶段执行完,若微任务过多会阻塞页面渲染;
*浏览器也会尽量保持60Hz的刷新率运行,也就是16.7ms刷新一帧,所以如果定时器的延迟时间大于16.7ms时,就会出现多次Paint的结果。
微任务和宏任务的区别
手写Promise
- 1.嵌套调用,第一个函数的输出往往是第二个函数的输入;
- 2.处理多个异步请求并发,开发时往往需要同步请求最终的结果。 1.消灭嵌套调用:通过 Promise 的链式调用可以解决; 2.合并多个任务的请求结果:使用 Promise.all 获取合并多个任务的错误处理。Promise 正是用一种更加友好的代码组织方式,解决了异步嵌套的问题。
作者:齐小神 链接:zhuanlan.zhihu.com/p/183801144 来源:知乎 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
Promise 的基本特征:promise 有三个状态:pending,fulfilled,or rejected;「规范 Promise/A+ 2.1」new promise时, 需要传递一个executor()执行器,执行器立即执行;executor接受两个参数,分别是resolve和reject;promise 的默认状态是 pending;promise 有一个value保存成功状态的值,可以是undefined/thenable/promise;「规范 Promise/A+ 1.3」promise 有一个reason保存失败状态的值;「规范 Promise/A+ 1.5」promise 只能从pending到rejected, 或者从pending到fulfilled,状态一旦确认,就不会再改变;promise 必须有一个then方法,then 接收两个参数,分别是 promise 成功的回调 onFulfilled, 和 promise 失败的回调 onRejected;「规范 Promise/A+ 2.2」如果调用 then 时,promise 已经成功,则执行onFulfilled,参数是promise的value;如果调用 then 时,promise 已经失败,那么执行onRejected, 参数是promise的reason;如果 then 中抛出了异常,那么就会把这个异常作为参数,传递给下一个 then 的失败的回调onRejected;
作者:齐小神
链接:https://zhuanlan.zhihu.com/p/183801144
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
const PENDING = 'PENDING';
const FULFILLED = 'FULFILLED';
const REJECTED = 'REJECTED';
class Promise {
constructor(executor) {
this.status = PENDING;
this.value = undefined;
this.reason = undefined;
// 存放成功的回调
this.onResolvedCallbacks = [];
// 存放失败的回调
this.onRejectedCallbacks= [];
let resolve = (value) => {
if(this.status === PENDING) {
this.status = FULFILLED;
this.value = value;
// 依次将对应的函数执行
this.onResolvedCallbacks.forEach(fn=>fn());
}
}
let reject = (reason) => {
if(this.status === PENDING) {
this.status = REJECTED;
this.reason = reason;
// 依次将对应的函数执行
this.onRejectedCallbacks.forEach(fn=>fn());
}
}
try {
executor(resolve,reject)
} catch (error) {
reject(error)
}
}
then(onFulfilled, onRejected) {
if (this.status === FULFILLED) {
onFulfilled(this.value)
}
if (this.status === REJECTED) {
onRejected(this.reason)
}
if (this.status === PENDING) {
// 如果promise的状态是 pending,需要将 onFulfilled 和 onRejected 函数存放起来,等待状态确定后,再依次将对应的函数执行
this.onResolvedCallbacks.push(() => {
onFulfilled(this.value)
});
// 如果promise的状态是 pending,需要将 onFulfilled 和 onRejected 函数存放起来,等待状态确定后,再依次将对应的函数执行
this.onRejectedCallbacks.push(()=> {
onRejected(this.reason);
})
}
}
}