重点
首先,宏观任务(任务),是JavaScript引擎的宿主发起的,把代码传递给JavaScript引擎,让引擎去执行,比如setTimeout、setInterval、click、控制台回车运行代码片段(evaluateScript)、script标签执行代码等,再次强调宏观任务(任务)是由宿主发起的任务,当然宏任务之间也有优先级,但是这个优先级是由宿主规定的,可能就是使用优先队列存储宏任务了。
补充: 宿主可能为浏览器、C、C++、Java等。
在c++中调用quickjs
— C++入个门欢迎看这个仓库呦
evaluateScript方法
Value evaluateScript(std::string_view buffer, const char * filename = "<eval>", unsigned eval_flags = 0)
{
assert(buffer.data()[buffer.size()] == '\0' && "eval buffer is not null-terminated"); // JS_Eval requirement
JSValue v = JS_Eval(ctx, buffer.data(), buffer.size(), filename, eval_flags);
js_std_loop(ctx);
return Value{ctx, v};
}
使用evaluateScript
auto &module = context.addModule("MyModule");
module.function<&println>("println");
context.eval("import * as my from 'MyModule'; globalThis.my = my;", "<import>", JS_EVAL_TYPE_MODULE);
string expr3 = "(new Promise(resolve => resolve()).then(() => {this.a = 100;my.println(`js this.a: ${this.a}`)}),function () {return this.a})";
Value t = context.evaluateScript(expr3);
cout << t.as<string>() << endl; // 输出看一下返回的是什么
println("------------------------分割线--------------------------------");
auto fn = (std::function<Value()>) context.evaluateScript(expr3);
Value fnRes = fn(); // 函数返回值
cout << "fnRes: " << fnRes.toJSON() << endl;
主要内容
如果我们去开发浏览器,或者使用JavaScript引擎,我们拿到一段代码首先会发送给JavaScript引擎,并要求引擎去执行。但是在宿主运行阶段,并不总是简单的就执行一次就行了,有时会再次、多次把代码传递给引擎并要求引擎执行,当然我们也可能为引擎提供一些api如setTimeout,允许引擎在特定时机执行,异步执行代码。
大致如果宿主还在运行,引擎会一直等待宿主提供JavaScript代码,并要求它执行。
在ES3和更早的JavaScript版本中,JavaScript引擎本身没有异步能力,宿主把代码发送给引擎,引擎就直接顺次执行了,只能通过宿主提供的setTimeout,在特定时机执行,异步执行代码。
ES6开始,JavaScript引入了Promise,不需要宿主,引擎本身就可以发起任务了。
根据JSCore引擎术语由宿主发起的任务称为宏观任务,由JavaScript引擎发起的任务称为微观任务。
通常JavaScript引擎等待宿主分配宏观任务,在操作系统中,这个等待的动作是一个循环,在node术语中称为事件循环。
quickJS Event Loop代码在这里,相关文章在这里
/* main loop which calls the user JS callbacks */
void js_std_loop(JSContext *ctx)
{
JSContext *ctx1;
int err;
for(;;) {
/* execute the pending jobs */
for(;;) {
err = JS_ExecutePendingJob(JS_GetRuntime(ctx), &ctx1);
if (err <= 0) {
if (err < 0) {
js_std_dump_error(ctx1);
}
break;
}
}
if (!os_poll_func || os_poll_func(ctx))
break;
}
}
这不就是在双重的死循环里先执行掉所有的 Job,然后调 os_poll_func 吗?可是,for 循环不会吃满 CPU 吗?这是个前端同学们容易误解的地方:在原生开发中,进程里即便写着个死循环,也未必始终在前台运行,可以通过系统调用将自己挂起。
例如,一个在死循环里通过 sleep 系统调用不停休眠一秒的进程,就只会每秒被系统执行一个 tick,其它时间里都不占资源。而这里的 os_poll_func 封装的,就是原理类似的 poll 系统调用(准确地说,用的其实是 select),从而可以借助操作系统的能力,使得只在【定时器触发、文件描述符读写】等事件发生时,让进程回到前台执行一个 tick,把此时应该运行的 JS 回调跑一遍,而其余时间都在后台挂起。在这条路上继续走下去,就能以经典的异步非阻塞方式来实现整个运行时啦。
— 从 JS 引擎到 JS 运行时(上)
整个循环就是在,”等待-执行”,这里每进行一次执行其实就是一个宏任务,大致可以理解为,宏观任务队列就相当于事件循环。
在宏观任务中,JavaScript的Promise还会产生异步代码,JavaScript必须保证这些异步代码在一个宏观任务中执行完成,因此每个宏观任务中又包含一个微观任务队列。
—- 图片来自《重学前端》
分析执行顺序
其中值得注意的点
1.promise函数参数
new Promise(resolve => {
console.log("0")
resolve()
})
2.同步代码,会先执行
3.遇到then就是异步,then回调什么时候执行,取决于什么时候resolve
4.遇到await就是异步
5.async函数嵌套执行与普通函数不同
6.async函数中await依次按顺序执行也与同步代码不同
分析示例
async函数嵌套执行与普通函数不同
/*
async 函数
与普通函数执行(调用栈,从里层向外层,逐层执行)有很大不同
*/
async function async1() {
/*{*/
console.log("async 1 start") /*这块相当于new Promise(resolve => {// 立即执行})中的函数参数,会立即执行 */
/*}*/
await async2(); // await 之后入队、
/*{*/
console.log("async 1 end") /*这块相当于.then((res) => {})中的异步回调,如果await 函数中仍有await,会一直到最后一层,最后一层的then入队微任务,然后跳出async函数,去执行外面的代码(比如会遇到新的promise,then入队微任务),然后将async其他层的函数按调用栈都入队微任务 */
/*}*/
}
async function async2() {
console.log("async2")
// let res = await new Promise((resolve) =>resolve("ok"))
await async3()
console.log("async2 end")
}
async function async3() {
console.log("async3")
// 最后一层await处,首先入当前宏任务中的微任务队列,
// 然后跳出async函数执行,去执行外面的语句,外面语句执行完成后,才会逐层入队
await async5()
console.log("async3 end")
}
async function async5() {
}
console.log("before")
async1()
console.log("after")
new Promise(function (resolve) {
console.log("promise1")
resolve()
}).then(function () {
console.log("promise2")
})
/**
* 宏任务
* before、async 1 start、async2、async3、after、promise1 这一行同步代码组成一个微任务
* async3 end、promise2、async2 end、async 1 end 按顺序入微任务队列
* async3 end
* promise2
* async2 end
* async 1 end 这是一个微任务
*
*
*/
async函数中await依次按顺序执行也与同步代码不同
/**
* 宏任务
* async 1 start、async2、promise1
* async 1 end、promise2 入队
* async 1 end、async3
* async3 end 入队
* promise2
* async3 end
*/
async function async1() {
console.log("async 1 start")
await async2(); // await 之后的内容入队
/*{*/console.log("async 1 end")
/* */await async3(); // await 之后的内容入队
/* *//*{*/console.log("async3 end") /*}*/
/*}*/
}
async function async2() {
console.log("async2")
}
async function async3() {
console.log("async3")
}
async1()
new Promise(function (resolve) {
console.log("promise1")
resolve()
}).then(function () {
console.log("promise2")
})
/**
* 宏任务
* async 1 start、async2、promise1 这一行同步代码组成一个微任务
* async 1 end、promise2 按顺序入微任务队列
* async 1 end、async3 这是一个微任务
* async3 end 按顺序入微任务队列
* promise2 这是一个微任务
* async3 end
*/
注
宏观任务与宏任务在文章中为同一概念
微观任务与微任务在文章中为同一概念
写在最后
如有问题欢迎指正