重点

首先,宏观任务(任务),是JavaScript引擎的宿主发起的,把代码传递给JavaScript引擎,让引擎去执行,比如setTimeout、setInterval、click、控制台回车运行代码片段(evaluateScript)、script标签执行代码等,再次强调宏观任务(任务)是由宿主发起的任务,当然宏任务之间也有优先级,但是这个优先级是由宿主规定的,可能就是使用优先队列存储宏任务了。
补充: 宿主可能为浏览器、C、C++、Java等。

在c++中调用quickjs

— C++入个门欢迎看这个仓库

evaluateScript方法

代码

  1. Value evaluateScript(std::string_view buffer, const char * filename = "<eval>", unsigned eval_flags = 0)
  2. {
  3. assert(buffer.data()[buffer.size()] == '\0' && "eval buffer is not null-terminated"); // JS_Eval requirement
  4. JSValue v = JS_Eval(ctx, buffer.data(), buffer.size(), filename, eval_flags);
  5. js_std_loop(ctx);
  6. return Value{ctx, v};
  7. }

使用evaluateScript

代码

  1. auto &module = context.addModule("MyModule");
  2. module.function<&println>("println");
  3. context.eval("import * as my from 'MyModule'; globalThis.my = my;", "<import>", JS_EVAL_TYPE_MODULE);
  4. string expr3 = "(new Promise(resolve => resolve()).then(() => {this.a = 100;my.println(`js this.a: ${this.a}`)}),function () {return this.a})";
  5. Value t = context.evaluateScript(expr3);
  6. cout << t.as<string>() << endl; // 输出看一下返回的是什么
  7. println("------------------------分割线--------------------------------");
  8. auto fn = (std::function<Value()>) context.evaluateScript(expr3);
  9. Value fnRes = fn(); // 函数返回值
  10. cout << "fnRes: " << fnRes.toJSON() << endl;

主要内容

如果我们去开发浏览器,或者使用JavaScript引擎,我们拿到一段代码首先会发送给JavaScript引擎,并要求引擎去执行。但是在宿主运行阶段,并不总是简单的就执行一次就行了,有时会再次、多次把代码传递给引擎并要求引擎执行,当然我们也可能为引擎提供一些api如setTimeout,允许引擎在特定时机执行,异步执行代码。

大致如果宿主还在运行,引擎会一直等待宿主提供JavaScript代码,并要求它执行。

在ES3和更早的JavaScript版本中,JavaScript引擎本身没有异步能力,宿主把代码发送给引擎,引擎就直接顺次执行了,只能通过宿主提供的setTimeout,在特定时机执行,异步执行代码。

ES6开始,JavaScript引入了Promise,不需要宿主,引擎本身就可以发起任务了。

根据JSCore引擎术语由宿主发起的任务称为宏观任务,由JavaScript引擎发起的任务称为微观任务。

通常JavaScript引擎等待宿主分配宏观任务,在操作系统中,这个等待的动作是一个循环,在node术语中称为事件循环。

在quickJS中这部分代码使用在这里

quickJS Event Loop代码在这里,相关文章在这里

  1. /* main loop which calls the user JS callbacks */
  2. void js_std_loop(JSContext *ctx)
  3. {
  4. JSContext *ctx1;
  5. int err;
  6. for(;;) {
  7. /* execute the pending jobs */
  8. for(;;) {
  9. err = JS_ExecutePendingJob(JS_GetRuntime(ctx), &ctx1);
  10. if (err <= 0) {
  11. if (err < 0) {
  12. js_std_dump_error(ctx1);
  13. }
  14. break;
  15. }
  16. }
  17. if (!os_poll_func || os_poll_func(ctx))
  18. break;
  19. }
  20. }

这不就是在双重的死循环里先执行掉所有的 Job,然后调 os_poll_func 吗?可是,for 循环不会吃满 CPU 吗?这是个前端同学们容易误解的地方:在原生开发中,进程里即便写着个死循环,也未必始终在前台运行,可以通过系统调用将自己挂起。
例如,一个在死循环里通过 sleep 系统调用不停休眠一秒的进程,就只会每秒被系统执行一个 tick,其它时间里都不占资源。而这里的 os_poll_func 封装的,就是原理类似的 poll 系统调用(准确地说,用的其实是 select),从而可以借助操作系统的能力,使得只在【定时器触发、文件描述符读写】等事件发生时,让进程回到前台执行一个 tick,把此时应该运行的 JS 回调跑一遍,而其余时间都在后台挂起。在这条路上继续走下去,就能以经典的异步非阻塞方式来实现整个运行时啦。
从 JS 引擎到 JS 运行时(上)

整个循环就是在,”等待-执行”,这里每进行一次执行其实就是一个宏任务,大致可以理解为,宏观任务队列就相当于事件循环。

在宏观任务中,JavaScript的Promise还会产生异步代码,JavaScript必须保证这些异步代码在一个宏观任务中执行完成,因此每个宏观任务中又包含一个微观任务队列。
16f70a9a51a65d5302166b0d78414d65.jpg
—- 图片来自《重学前端》

分析执行顺序

其中值得注意的点

1.promise函数参数

  1. new Promise(resolve => {
  2. console.log("0")
  3. resolve()
  4. })

Promise参数会立即执行。

2.同步代码,会先执行

同步代码是微任务

3.遇到then就是异步,then回调什么时候执行,取决于什么时候resolve

Promise发起的异步代码是微任务

4.遇到await就是异步

Promise发起的异步代码是微任务

5.async函数嵌套执行与普通函数不同

6.async函数中await依次按顺序执行也与同步代码不同

分析示例

async函数嵌套执行与普通函数不同

  1. /*
  2. async 函数
  3. 与普通函数执行(调用栈,从里层向外层,逐层执行)有很大不同
  4. */
  5. async function async1() {
  6. /*{*/
  7. console.log("async 1 start") /*这块相当于new Promise(resolve => {// 立即执行})中的函数参数,会立即执行 */
  8. /*}*/
  9. await async2(); // await 之后入队、
  10. /*{*/
  11. console.log("async 1 end") /*这块相当于.then((res) => {})中的异步回调,如果await 函数中仍有await,会一直到最后一层,最后一层的then入队微任务,然后跳出async函数,去执行外面的代码(比如会遇到新的promise,then入队微任务),然后将async其他层的函数按调用栈都入队微任务 */
  12. /*}*/
  13. }
  14. async function async2() {
  15. console.log("async2")
  16. // let res = await new Promise((resolve) =>resolve("ok"))
  17. await async3()
  18. console.log("async2 end")
  19. }
  20. async function async3() {
  21. console.log("async3")
  22. // 最后一层await处,首先入当前宏任务中的微任务队列,
  23. // 然后跳出async函数执行,去执行外面的语句,外面语句执行完成后,才会逐层入队
  24. await async5()
  25. console.log("async3 end")
  26. }
  27. async function async5() {
  28. }
  29. console.log("before")
  30. async1()
  31. console.log("after")
  32. new Promise(function (resolve) {
  33. console.log("promise1")
  34. resolve()
  35. }).then(function () {
  36. console.log("promise2")
  37. })
  38. /**
  39. * 宏任务
  40. * before、async 1 start、async2、async3、after、promise1 这一行同步代码组成一个微任务
  41. * async3 end、promise2、async2 end、async 1 end 按顺序入微任务队列
  42. * async3 end
  43. * promise2
  44. * async2 end
  45. * async 1 end 这是一个微任务
  46. *
  47. *
  48. */

async函数中await依次按顺序执行也与同步代码不同

  1. /**
  2. * 宏任务
  3. * async 1 start、async2、promise1
  4. * async 1 end、promise2 入队
  5. * async 1 end、async3
  6. * async3 end 入队
  7. * promise2
  8. * async3 end
  9. */
  10. async function async1() {
  11. console.log("async 1 start")
  12. await async2(); // await 之后的内容入队
  13. /*{*/console.log("async 1 end")
  14. /* */await async3(); // await 之后的内容入队
  15. /* *//*{*/console.log("async3 end") /*}*/
  16. /*}*/
  17. }
  18. async function async2() {
  19. console.log("async2")
  20. }
  21. async function async3() {
  22. console.log("async3")
  23. }
  24. async1()
  25. new Promise(function (resolve) {
  26. console.log("promise1")
  27. resolve()
  28. }).then(function () {
  29. console.log("promise2")
  30. })
  31. /**
  32. * 宏任务
  33. * async 1 start、async2、promise1 这一行同步代码组成一个微任务
  34. * async 1 end、promise2 按顺序入微任务队列
  35. * async 1 end、async3 这是一个微任务
  36. * async3 end 按顺序入微任务队列
  37. * promise2 这是一个微任务
  38. * async3 end
  39. */

宏观任务与宏任务在文章中为同一概念
微观任务与微任务在文章中为同一概念

写在最后

如有问题欢迎指正