Code Monkey home page Code Monkey logo

Comments (36)

zhongdeming428 avatar zhongdeming428 commented on September 21, 2024 44

笔记总结

微任务与宏任务

  • 宏任务主要包含: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.

harry-liu avatar harry-liu commented on September 21, 2024 11
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.

woshixiaoqianbi avatar woshixiaoqianbi commented on September 21, 2024 5

试了下,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.

dwqs avatar dwqs commented on September 21, 2024 4

自己动手实现 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.
    }
}
// ...

@njleonzhang

from blog.

yaodingyd avatar yaodingyd commented on September 21, 2024 4

requestAnimationFrame不属于task,它是浏览器渲染过程的一步,和task/microtask的执行是分离的

from blog.

Huooo avatar Huooo commented on September 21, 2024 4

非常感谢这篇文章的详解以及讨论,学习到了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中这两者的执行顺序??

  1. 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
  2. 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

建议在浏览器中直接打断点走,可以很直接看出每一轮的执行顺序以及对应的值,如下:

image

再从 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.

fi3ework avatar fi3ework commented on September 21, 2024 4

另外

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.

dwqs avatar dwqs commented on September 21, 2024 2

@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.

dwqs avatar dwqs commented on September 21, 2024 2

已修改 @yaodingyd

from blog.

watsonnnnn avatar watsonnnnn commented on September 21, 2024 1

tick有些疑问,什么叫一次tick?如果一次循环叫一次tick,那为什么不直接叫nextLoop呢?一次循环指的是清空队列,还是取队列里的一个event并执行?

from blog.

julyL avatar julyL commented on September 21, 2024 1
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对象的处理,如下图
image

可以看到thenable对象的then方法(then$$1变量)是在asap回调中执行的。asap方法是一个用于异步执行回调的函数。会在下一个事件循环中执行传入的回调函数。

当Promise.resolve处理thenable对象时,解析thenable对象这个操作会放到一个事件循环(微任务)中去执行,所以t1比t2先执行。

from blog.

njleonzhang avatar njleonzhang commented on September 21, 2024

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.

njleonzhang avatar njleonzhang commented on September 21, 2024

@dwqs 学到了。

from blog.

whapply avatar whapply commented on September 21, 2024

楼上的这个

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.

sunyongjian avatar sunyongjian commented on September 21, 2024

@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.

dwqs avatar dwqs commented on September 21, 2024

@Huooo 第二个问题 你可以看下上面「部分相关实现」我贴出的代码

from blog.

Huooo avatar Huooo commented on September 21, 2024

Event Loop的理解有点磕磕绊绊的,主要担心自己理解的不是正确的,但是关于Promise.resolve()源码的部分已经看明白了,非常感谢 @dwqs

from blog.

WangBiaoxuan avatar WangBiaoxuan commented on September 21, 2024

@dwqs 怎么MessageChannel既在microTask又在macTask中呀?

from blog.

dwqs avatar dwqs commented on September 21, 2024

@WangBiaoxuan 已纠正 谢谢指出

from blog.

fi3ework avatar fi3ework commented on September 21, 2024

MessageChannel 是属于 microTask 啊

from blog.

sfsoul avatar sfsoul commented on September 21, 2024

@fi3ework hey,man!you are right

from blog.

qingtianiii avatar qingtianiii commented on September 21, 2024

非常感谢!!讲的很详细,大致都明白了!

不过我还有一点疑惑,希望大佬能解答一下

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.

webjohnjiang avatar webjohnjiang commented on September 21, 2024

@qingtianiii 看 then的注册顺序:首先注册的是 console.log(2) 这个then,接下来注册 console.log(t) 这个then,所以先输出2 (在2执行时又注册了一个then),再输出1. 1输出结束之后,发现microtask队列还有一个新的task,则执行它输出 4.

from blog.

fundatou avatar fundatou commented on September 21, 2024

大佬,MutaionObserver 有个拼写错误哈~

from blog.

zaq158 avatar zaq158 commented on September 21, 2024
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.

Yangzhedi avatar Yangzhedi commented on September 21, 2024

图破了。。。

from blog.

xiaolong-zh avatar xiaolong-zh commented on September 21, 2024

setTimeout(() => {
console.log(2)
}, 2)
setTimeout(() => {
console.log(1)
}, 1)
setTimeout(() => {
console.log(0)
}, 0)
这个要怎么去解释

from blog.

vnues avatar vnues commented on September 21, 2024

执行队列里面是所有同步代码的任务,事件队列里面是所有异步代码的宏任务,而我们的微任务,是处在两个队列之间。

不要这样理解啊大哥

from blog.

7gugu avatar 7gugu commented on September 21, 2024

这里的措辞有问题:“防止主线程的不阻塞”的意思就是要主线程阻塞,但是引入Event loop不就是为了解决阻塞问题吗?此处应该是“防止主线程的阻塞”,才是正确的吧?

from blog.

blingbling-110 avatar blingbling-110 commented on September 21, 2024
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对象的处理,如下图 image

可以看到thenable对象的then方法(then$$1变量)是在asap回调中执行的。asap方法是一个用于异步执行回调的函数。会在下一个事件循环中执行传入的回调函数。

当Promise.resolve处理thenable对象时,解析thenable对象这个操作会放到一个事件循环(微任务)中去执行,所以t1比t2先执行。

这才是正解,顺便附上源码地址

from blog.

Jetmet avatar Jetmet commented on September 21, 2024

from blog.

nieshuangyan avatar nieshuangyan commented on September 21, 2024

from blog.

nieshuangyan avatar nieshuangyan commented on September 21, 2024

from blog.

lio-mengxiang avatar lio-mengxiang commented on September 21, 2024

楼主逻辑梳理是错的,首先PromiseA+规范并不是浏览器实现规范,浏览器实现的是ecma对promise的要求。

然后Promise.resolve()本身就已经有微任务进去了,可以看网上的一些v8对promise的实现代码,还有更复杂的就是当promise返回一个Promise时,微任务队列有点绕,这里就不多说了

from blog.

nieshuangyan avatar nieshuangyan commented on September 21, 2024

from blog.

Jetmet avatar Jetmet commented on September 21, 2024

from blog.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.