Comments (36)
笔记总结
微任务与宏任务
-
宏任务主要包含:script(整体代码)、setTimeout、setInterval、I/O、UI交互事件、setImmediate(Node.js 环境)
-
微任务主要包含:Promise、MutaionObserver、process.nextTick(Node.js 环境)
我们的JavaScript的执行过程是单线程的,所有的任务可以看做存放在两个队列中——执行队列和事件队列。
执行队列里面是所有同步代码的任务,事件队列里面是所有异步代码的宏任务,而我们的微任务,是处在两个队列之间。
当JavaScript执行时,优先执行完所有同步代码,遇到对应的异步代码,就会根据其任务类型存到对应队列(宏任务放入事件队列,微任务放入执行队列之后,事件队列之前);当执行完同步代码之后,就会执行位于执行队列和事件队列之间的微任务,然后再执行事件队列中的宏任务。
实例
console.log('script start');
setTimeout(function() {
console.log('timeout1');
}, 10);
new Promise(resolve => {
console.log('promise1');
resolve();
setTimeout(() => console.log('timeout2'), 10);
}).then(function() {
console.log('then1')
})
console.log('script end');
以上代码来源于博主的该篇博客,比较具有代表性,因为包含了宏任务、微任务、同步代码。在我们分析输出时,可以给所有的任务划分归类,结果如下:
console.log('script start'); //同步输出——1
setTimeout(function() {
console.log('timeout1'); //异步宏任务,推入事件队列——5
}, 10);
new Promise(resolve => {
console.log('promise1'); //同步输出——2
resolve(); //同步执行
setTimeout(() => console.log('timeout2'), 10); //异步宏任务,推入事件队列——6
}).then(function() {
console.log('then1') //异步微任务, 在执行队列之后,事件队列之前执行——4
})
console.log('script end'); //同步输出——3
结尾
这是我在阅读博主的博文之后,根据自己的想法理解所写出来的,难免存在错误,敬请大家指出!
from blog.
console.log('script start');
setTimeout(function() {
console.log('timeout1');
new Promise(resolve => {
console.log('promise2');
resolve();
}).then(function() {
console.log('then2')
})
}, 10);
new Promise(resolve => {
console.log('promise1');
setTimeout(() => console.log('timeout2'), 10);
resolve();
}).then(function() {
console.log('then1')
})
console.log('script end');
感谢博主
稍微再改变一下,感觉理解更好一些
from blog.
试了下,setTimout和setImmediate却不是同源的,
new Promise(()=>{
setTimeout(()=>{
console.log(1);
});
setImmediate(()=>{
console.log(2);
})
resolve(1);
}).then(()=>{
setTimeout(()=>{
console.log(3);
});
setImmediate(()=>{
console.log(4);
})
}).then(()=>{
setTimeout(()=>{
console.log(5);
});
setImmediate(()=>{
console.log(6);
})
})
setImmediate和setInterval有自己的任务队列
from blog.
从 自己动手实现 ES6 Promise 一文可以看到部分相关实现:
// ...
// 根据 x 值,解析 promise 状态
resolveProcedure(promise, x)function resolveProcedure({ resolve, reject, promise2 }, x) {
// 2.3.1 If promise and x refer to the same object, reject promise with a TypeError as the reason.
if (promise2 === x) {
reject(new TypeError(x));
}
if (x instanceof ES6Promise) { // 2.3.2 If x is a promise, adopt its state
x.then(value => resolveProcedure({resolve, reject, promise2}, value), reason => reject(reason));
} else if ((typeof x === 'object' && x !== null) || (typeof x === 'function')) { // 2.3.3
let resolvedOrRejected = false;
try {
let then = x.then; // 2.3.3.1 Let then be x.then
if (typeof then === 'function') { // 2.3.3 If then is a function, call it with x as this, first argument resolvePromise, and second argument rejectPromise, where:
then.call(x, value => {
if (!resolvedOrRejected) {
resolveProcedure({ resolve, reject, promise2 }, value); // 2.3.3.3.1 If/when resolvePromise is called with a value y, run [[Resolve]](promise, y).
resolvedOrRejected = true;
}
// 2.3.3.3.3 If both resolvePromise and rejectPromise are called, or multiple calls to the same argument are made, the first call takes precedence, and any further calls are ignored.
}, reason => {
if (!resolvedOrRejected) {
reject(reason); // 2.3.3.3.2 If/when rejectPromise is called with a reason r, reject promise with r.
resolvedOrRejected = true;
}
// 2.3.3.3.3 If both resolvePromise and rejectPromise are called, or multiple calls to the same argument are made, the first call takes precedence, and any further calls are ignored.
});
} else { // 2.3.3.4 If then is not a function, fulfill promise with x.
resolve(x);
}
} catch (e) {
if (!resolvedOrRejected) {
// 2.3.3.2 If retrieving the property x.then results in a thrown exception e, reject promise with e as the reason.
// 2.3.3.4 If calling then throws an exception e
reject(e);
}
}
} else {
resolve(x); // 2.3.4 If x is not an object or function, fulfill promise with x.
}
}
// ...
from blog.
requestAnimationFrame不属于task,它是浏览器渲染过程的一步,和task/microtask的执行是分离的
from blog.
非常感谢这篇文章的详解以及讨论,学习到了Event Loop的很多细节。但是还有一处不明,还请帮忙解释一下。非常感谢你的时间。
// example 1
new Promise(resolve => {
resolve(1)
Promise.resolve().then(() => console.log(42)) // then1
console.log(4)
}).then(t => console.log(t)) // then2
console.log(3)
// 输出结果依此为:4, 3, 42, 1
// example 2
let thenable = {
then: (resolve, reject) => {
resolve(42)
}
}
new Promise(resolve => {
resolve(1)
Promise.resolve(thenable).then((t) => console.log(t)) // then1
console.log(4)
}).then(t => console.log(t)) // then2
console.log(3)
// 输出结果依此为:4, 3, 1, 42
问题是:不明白在Promise.resolve()的参数是“无参数”和“thenable对象”时,没搞明白Event Loop中这两者的执行顺序??
- example 1是阮一峰老师在Promise.resolve()的四种参数情况中的“无参数”,直接返回一个resolved状态的 Promise 对象,此时resolve的 Promise 对象是在本轮“事件循环”(event loop)的结束时,而不是在下一轮“事件循环”的开始时。我理解到的是执行到‘Promise.resolve().then(() => console.log(42))’的回调函数then()被立即放入Microtask Queue中记为then1,然后继续执行外层回调函数then(),放入Microtask Queue中记为then2,所以then1优先于then2执行,先输出42后输出1
- example 1是传入了“thenable对象”,那么按照作者文章解释的,此时‘Promise.resolve(thenable).then((t) => console.log(t))’的回调函数也应该是放入Microtask Queue中记为then1,外层的回调函数then(),放入Microtask Queue中记为then2,所以我认为的then1优先于then2执行,先输出42后输出1,但是实际上输出结果是1, 42
上面的讨论感觉并没有解答我真正的疑惑,所以还请解释详细一些,非常感谢。 @dwqs
20190225: 一位小伙伴(也没留github id)在 SF 上让我回来把我想通的思路再说一下。惭愧,我都快忘记我问过啥了,我就借机又重温了一下,非常感谢。
总结一下我的问题:不明白在Promise.resolve()的参数是“非 thenable 对象”和“ thenable 对象”时的执行原理,以至于对例子中的执行顺序不确定。
这次换个例子便于理解
// demo
const thenable = {
then: function(resolve, reject) {
console.log('flag-0');
resolve(1);
console.log('flag-1');
}
};
const p1 = Promise.resolve();
p1.then(() => {
console.log(2);
});
console.log('flag-2');
const p2 = Promise.resolve(thenable);
p2.then((value) => {
console.log(value);
});
setTimeout(() => {
console.log('timeout');
}, 0);
const p3 = Promise.resolve();
p3.then(() => {
console.log(3);
});
console.log('flag-3');
// 输出结果依此为:flag-2, flag-3, 2, flag-0, flag-1, 3, 1, timeout
先从 Event Loop 的角度说一下个人理解:
Event Loop 的执行机制,我也是看这篇文章的作者学习的,就不多说,只说这个例子里面的时间循环。
第一轮:
script:[ console.log('flag-2');
, console.log('flag-3');
]
此时:p1 ( resolved ), p2 ( pending ), p3 ( resolved )
第二轮:
micro-task: [ p1.then()
, thenable.then()
, p3.then()
, p2.then()
]
macro-task: setTimeout()
因此输出结果顺序为:flag-2, flag-3, 2, flag-0, flag-1, 3, 1, timeout
建议在浏览器中直接打断点走,可以很直接看出每一轮的执行顺序以及对应的值,如下:
再从 Promise Promise.resolve() 的角度说一下个人理解:
The Promise.resolve() method returns a Promise object that is resolved with a given value. If the value is a promise, that promise is returned; if the value is a thenable (i.e. has a "then" method), the returned promise will "follow" that thenable, adopting its eventual state; otherwise the returned promise will be fulfilled with the value. This function flattens nested layers of promise-like objects (e.g. a promise that resolves to a promise that resolves to something) into a single layer.
Promise.resolve(thenable), Promise.resolve(thenable) 的最终状态值需要 "follow" 这个 thenable 对象, 使用它的最终状态( result ),我理解为 fulfill/reject promise
with the result,需要等 thenable 对象的最终状态完成之后才能完成 Promise.resolve(thenable) 的状态;然而 Promise.resolve() ,规范上是直接 fulfill promise with value。相比来说,同样是 Promise.resolve 动作,Promise.resolve() 在第一轮 Event Loop 的时候就已经完成最终状态,仅仅等待第二轮 Event Loop 调用回调函数,但是 Promise.resolve(thenable) 却是在第二轮 Event Loop 时先完成 thenable 的状态变更,然后 Promise.resolve(thenable) 才能完成自己的状态,调用自己的回调函数(这个回调函数是在第一轮 Event Loop 时注册进去的)。
以上是初步的理解,后面想再进一步学习一下 Event Loop 和 Promise,更好的理解,到时候再来这里放链接。我觉得有些时候英文的那个词可能编码者更容易理解,说成中文,不太容易对号入座,所以看规范更好学习。欢迎交流。
from blog.
另外
new Promise(resolve => {
resolve(1);
Promise.resolve().then(() => console.log(2));
console.log(4)
}).then(t => console.log(t));
console.log(3);
之所以 2 比 1 要早,是因为 then(() => console.log(2));
的执行在 .then(t => console.log(t)); console.log(3);
之前,所以在 Promise 的实现中,输出 2 对应的事件更早的注册到了 microtask queue 中。
from blog.
@njleonzhang 你看下阮老师对 Promise.resolve 的表述:
如果参数是个非 thenable 对象或者不是一个对象,也是返回一个
resolved
状态的 Promise
示例如下:
let thenable = {
then: function(resolve, reject) {
resolve(42);
}
};
new Promise(resolve => {
resolve(1);
Promise.resolve(thenable).then((t) => {
// t2
console.log(t)
});
console.log(4)
}).then(t => {
// t1
console.log(t)
});
console.log(3);
//输出 4 3 1 42
from blog.
已修改 @yaodingyd
from blog.
tick有些疑问,什么叫一次tick?如果一次循环叫一次tick,那为什么不直接叫nextLoop呢?一次循环指的是清空队列,还是取队列里的一个event并执行?
from blog.
let thenable = {
then: (resolve, reject) => {
resolve(2)
}
}
new Promise(resolve => {
resolve(1)
Promise.resolve(thenable).then((t) => console.log(t)) // t2
console.log(4)
}).then(t => console.log(t)) // t1
console.log(3)
根据源码进行解释:es6-promise.js库源码中对于thenable对象的处理,如下图
可以看到thenable对象的then方法(then$$1变量)是在asap回调中执行的。asap方法是一个用于异步执行回调的函数。会在下一个事件循环中执行传入的回调函数。
当Promise.resolve处理thenable对象时,解析thenable对象这个操作会放到一个事件循环(微任务)中去执行,所以t1比t2先执行。
from blog.
Promise.resolve 方法允许调用时不带参数,直接返回一个resolved 状态的 Promise 对象。立即 resolved 的 Promise 对象,是在本轮“事件循环”(event loop)的结束时,而不是在下一轮“事件循环”的开始时。
new Promise(resolve => {
resolve(1);
Promise.resolve(1).then(() => { // 结果并不会不一样啊
// t2
console.log(2)
});
console.log(4)
}).then(t => {
// t1
console.log(t)
});
console.log(3);
按你这个解释似乎有点矛盾。不过感觉上就是t2的then的代码先执行到了, 所以t2的then被先加入了队列。😂
from blog.
@dwqs 学到了。
from blog.
楼上的这个
console.log('script start');
setTimeout(function() {
console.log('timeout1');
new Promise(resolve => {
console.log('promise2');
resolve();
}).then(function() {
console.log('then2')
})
}, 10);
new Promise(resolve => {
console.log('promise1');
setTimeout(() => console.log('timeout2'), 10);
resolve();
}).then(function() {
console.log('then1')
})
console.log('script end');
在浏览器中执行的结果 和在node环境中执行的结果,并不一致。 有大佬解释下吗
from blog.
@sideFlower 浏览器和 Node.js 的事件循环机制是有区别的。单这道题来说,区别在于浏览器是把 macro task 1, 2 加入队列,挨个执行,并把 macro 中的 micro 执行。也就是 timer1(macro) 先执行,其中的 promise then(micro) 再执行,完毕后再跑 timer2。而 node 中的 micro task 是在 node 事件循环的各个阶段之间执行,也就是主线程跑完后,会把 micro 池中的事件清空。然后是 timers 阶段,也就是定时器这种,按照加入事件池的先后顺序执行,此时 micro task 加入新的 promise.then,直到所有的定时器执行完,才会从 micro task 取出任务,挨个执行,所以才会最后调用 then2。
这是从这道题来说的不同,具体你可以从网上搜一下 浏览器和 node 环境事件循环的不同。
from blog.
@Huooo 第二个问题 你可以看下上面「部分相关实现」我贴出的代码
from blog.
Event Loop的理解有点磕磕绊绊的,主要担心自己理解的不是正确的,但是关于Promise.resolve()源码的部分已经看明白了,非常感谢 @dwqs
from blog.
@dwqs 怎么MessageChannel既在microTask又在macTask中呀?
from blog.
@WangBiaoxuan 已纠正 谢谢指出
from blog.
MessageChannel 是属于 microTask 啊
from blog.
@fi3ework hey,man!you are right
from blog.
非常感谢!!讲的很详细,大致都明白了!
不过我还有一点疑惑,希望大佬能解答一下
setTimeout(() => {
console.log(0)
})
new Promise(resolve => {
resolve(1)
Promise.resolve().then(t => {
console.log(2)
Promise.resolve().then(t => {
console.log(4)
})
})
console.log(3)
})
.then(t => {
console.log(t)
})
console.log(5)
输出顺序是 3 5 2 1 4 0,为什么4会比1先输出?不是说 Promise.resolve().then 会更先执行吗?
@dwqs
from blog.
@qingtianiii 看 then的注册顺序:首先注册的是 console.log(2) 这个then,接下来注册 console.log(t) 这个then,所以先输出2 (在2执行时又注册了一个then),再输出1. 1输出结束之后,发现microtask队列还有一个新的task,则执行它输出 4.
from blog.
大佬,MutaionObserver 有个拼写错误哈~
from blog.
new Promise(resolve => {
resolve(1);
Promise.resolve().then(() => {
// t2
console.log(2)
});
console.log(4)
}).then(t => {
// t1
console.log(t)
});
console.log(3);
在chrome中输出是4321
在firefox中输出是4312,这个是为什么?浏览器内核?
from blog.
图破了。。。
from blog.
setTimeout(() => {
console.log(2)
}, 2)
setTimeout(() => {
console.log(1)
}, 1)
setTimeout(() => {
console.log(0)
}, 0)
这个要怎么去解释
from blog.
执行队列里面是所有同步代码的任务,事件队列里面是所有异步代码的宏任务,而我们的微任务,是处在两个队列之间。
不要这样理解啊大哥
from blog.
这里的措辞有问题:“防止主线程的不阻塞”的意思就是要主线程阻塞,但是引入Event loop不就是为了解决阻塞问题吗?此处应该是“防止主线程的阻塞”,才是正确的吧?
from blog.
let thenable = { then: (resolve, reject) => { resolve(2) } } new Promise(resolve => { resolve(1) Promise.resolve(thenable).then((t) => console.log(t)) // t2 console.log(4) }).then(t => console.log(t)) // t1 console.log(3)根据源码进行解释:es6-promise.js库源码中对于thenable对象的处理,如下图
可以看到thenable对象的then方法(then$$1变量)是在asap回调中执行的。asap方法是一个用于异步执行回调的函数。会在下一个事件循环中执行传入的回调函数。
当Promise.resolve处理thenable对象时,解析thenable对象这个操作会放到一个事件循环(微任务)中去执行,所以t1比t2先执行。
这才是正解,顺便附上源码地址
from blog.
from blog.
from blog.
from blog.
楼主逻辑梳理是错的,首先PromiseA+规范并不是浏览器实现规范,浏览器实现的是ecma对promise的要求。
然后Promise.resolve()本身就已经有微任务进去了,可以看网上的一些v8对promise的实现代码,还有更复杂的就是当promise返回一个Promise时,微任务队列有点绕,这里就不多说了
from blog.
from blog.
from blog.
Related Issues (20)
- [译]JavaScript 的时间消耗 HOT 4
- Webpack 4 不完全迁移指北 HOT 23
- Nginx 上配置 HTTPS 环境 HOT 8
- 列表数据的展示优化 HOT 2
- 处理 undefined 值的7个建议 HOT 1
- How to escape async/await hell HOT 8
- 数制基础 HOT 1
- ES6 Class Methods 定义方式的差异 HOT 15
- 浅说 XSS 和 CSRF HOT 18
- 浅说移动前端中 Viewport 和 Viewport units HOT 4
- 浅说虚拟列表的实现原理 HOT 25
- react-tiny-virtual-list的源码解读 HOT 5
- react-virtualized 组件的虚拟列表实现
- react-virtualized 组件的虚拟列表优化分析 HOT 1
- 图片和视频的懒加载 HOT 2
- 从 Hello World 看 RN 的启动流程(一) HOT 2
- 从 Hello World 看 RN 的启动流程(二) HOT 1
- 这样就产生了反射型 XSS 攻击。攻击者可以注入任意的恶意脚本进行攻击,可能注入恶作剧脚本,或者注入能获取用户隐私数据(如cookie)的脚本,这取决于攻击者的目的。
- dom型xss攻击中,我没有理解具体的危害。输入内容是用户自己控制的,即使他输入恶意内容,又能干些什么呢?能用一个具体的案例,讲下攻击者的什么行为给受害者造成了什么危害吗? HOT 1
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from blog.