再探Promise之Promise与微任务

再探Promise之Promise与微任务

时常会遇到一些判断代码输出顺序的异步问题,主要会有promise+定时器、纯promise、await+promise、nextTick+Promise(node)等类型,这篇文章会逐个进行研究

目前Q&A(看看有你需要了解的部分吗)

  • Q1:then的回调函数什么时候进入的微任务队列?
  • Q2:Await/Async是怎样对待微任务的?
  • Q3:then回调函数如果返回一个Promise/thenable,会造成什么影响吗?
  • Q4:Promise.resolve()new Promise((_res,_rej)=>(_res()))有什么不同?
  • Q5:在Promie中'递归'resolve(Promise)会怎样?

纯promise

示例一(链式调用创Promise)

ts 复制代码
new Promise((resolve, reject) => {
	console.log(1);
	resolve(1);
})
	.then(a => {
		console.log(2);
		new Promise((resolve, reject) => {
			//内层Promise
			console.log(3);
			resolve(1);
		})
			.then(c => {
				console.log(4);
			})
			.then(d => {
				console.log(6);
			});
	})
	.then(b => {
		console.log(5);
	});
export {};

这部分很容易能得出下面打印过程

  1. 首先script宏任务开始,打印1,并将Promise状态变为终态
  2. 继续执行,遇到第一个then(a),由于调用这个then的promise已经为终态,所以a直接加入微任务队列,继续执行,调用b时,由于b对应的promise还是pending,所以放入调用then(b)的promise的缓存队列中
  3. 第一轮宏任务执行完毕,开始扫描微任务队列,此时微任务队列为[a]
  4. 从微任务队列中取出a,执行a,打印2,打印3,内层Promise由于也resolve()变为终态
  5. 继续执行,遇到内层Promise的then(c),由于调用c的promise已变为终态,所以c直接进入微任务队列,此时微任务队列:[c]
  6. 继续执行,遇到d,此时调用d的promise还处于pending,所以放入调用d所对应的promise的缓存队列
  7. a执行完毕,b所对应的promise进入终态,b进入微任务队列,此时微任务队列为[c,b]
  8. 取出c,执行c,打印4,c执行完毕,d所对应的promise变为终态d,d进入微任务队列,此时微任务队列为[b,d]
  9. 取出b,执行b,打印5,b执行完毕
  10. 取出d,执行d,打印6,d执行完毕
  11. 本次tick结束

promise+定时器

示例一(宏任务创建微任务)

ts 复制代码
console.log('script-宏任务-start');
const p11 = new Promise((_res, _rej) => {
	console.log('promise-p11-setTimeout-before');
	setTimeout(() => {
		console.log('promise-p11-setTimeout-start');
		_res(1);
		console.log('promise-p11-setTimeout-end');
	}, 1000);
	console.log('promise-p11-setTimeout-after');
});
p11.then(function mirrTaskFn(v) {
	console.log('p11-then');
});
console.log('script-宏任务-end');
export {};

从宏任务开始,遇到微任务先放进微任务队列(根据微任务优先级可以进行"插队"),等本轮宏任务执行完毕,挨个执行微任务,中间如果又产生微任务依旧在本轮执行完,什么时候微任务队列为空的时候,再执行下一轮宏任务
所以执行流程为

  • 第一轮
    • 宏任务:script-宏任务-start->promise-p11-setTimeout-before->promise-p11-setTimeout-after->script-宏任务-end
    • 微任务:未创建微任务
  • 第二轮
    • 宏任务:promise-p11-setTimeout-start->promise-p11-setTimeout-end
    • 微任务:创建微任务一个,微任务队列[mirrTaskFn],等待宏任务执行完毕,清空微任务队列,第二轮打印总的打印顺序为promise-p11-setTimeout-start->promise-p11-setTimeout-end->p11-then

示例二(微任务创建微任务)

ts 复制代码
console.log('script-宏任务-start');
const p_1 = new Promise((_res, _rej) => {
	console.log('p_1 setTimeout before');
	setTimeout(() => {
		console.log('p_1 setTimeout start');
		_res(1);
	});
});
const p_1_2 = p_1.then(function mirrTaskFn_1(v) {
	console.log('p_1-then');
});
const p_1_3 = p_1_2.then(function mirrTaskFn_1_2(v) {
	console.log('p_1_2-then');
});

const p_2 = new Promise((_res, _rej) => {
	_res(1);
});
const p_2_1 = p_2.then(function mirrTaskFn_2(v) {
	console.log('p_2-then');
});
const p_2_2 = p_2_1.then(function mirrTaskFn_2_1(v) {
	console.log('p_2_1-then');
});

console.log('script-宏任务-end');
export {};

执行流程为:

  • 第一轮
    • 宏任务:script-宏任务-start->p_1 setTimeout before->script-宏任务-end
    • 微任务:
      • 由于在调用p_2.then时,p_2已经变为了终态,所以直接加入微队列,即[mirrTaskFn_2]
      • 在本轮宏任务执行完毕之后将会执行微任务队列任务,在执行mirrTaskFn_2之后,p_2_1变为终态,所以会直接将mirrTaskFn_2_1加入微队列,即在mirrTaskFn_2执行完毕之后,微任务队列变为[mirrTaskFn_2_1]
      • 等到mirrTaskFn_2_1执行完毕,没有新的微任务创建,第一轮执行完成。
      • 第一轮总的打印顺序为script-宏任务-start->p_1 setTimeout before->script-宏任务-end->p_2-then->p_2_1-then
  • 第二轮
    • 宏任务:p_1 setTimeout start
    • 微任务:由于在第一轮已经调用了p_1.then和p_1_2.then,此时mirrTaskFn_1mirrTaskFn_1_2已经在各自的缓存队列([[PromiseFulfillReactions]])中了,现在p_1已经变为了终态,那么mirrTaskFn_1将会被加入微队列,此时微队列为[mirrTaskFn_1]。
    • 等到mirrTaskFn_1执行完毕,返回的promise就变为终态了,此时mirrTaskFn_1_2进入微队列,即[mirrTaskFn_1_2]。
    • 等到mirrTaskFn_2_1执行完毕,没有新的微任务创建,第二轮执行完成。
    • 第二轮总的打印顺序为p_1 setTimeout start->p_1-then->p_1_2-then

Q1:then的回调函数什么时候进入的微任务队列?

需要分为两种情况

  • 如果调用then的时候promise是pending状态,那么then的回调不会被添加微任务,而是进入[[PromiseFulfill/RejectReactions]] 列表里缓存起来,缓存起来的是条链表(为了处理多个then的链式调用)等到promise的状态发生更改(reslove/reject)时,会将该列表中的链表给取出来挨个放进微任务中(放到下轮事件循环处理)

    ts 复制代码
    const p11 = new Promise((_res, _rej) => {
    	setTimeout(() => {
    		_res(1);
    	}, 1000);
    });
    p11.then(function mirrTaskFn(v){
    	console.log('p11-then');
    });
  • 如果调用then的时候promise已经是最终态,那么直接放入微任务队列中(本轮事件循环会处理)

    ts 复制代码
    const p11 = new Promise((_res, _rej) => {
    	_res(1);
    });
    p11.then(function mirrTaskFn(v){
    	console.log('p11-then');
    });

Await + Promise

示例一(入门级)

ts 复制代码
async function async1() {
	console.log('async1 start');
	await async2();
	console.log('async1 end');
}
async function async2() {
	console.log('async2');
}

console.log('script start');

setTimeout(function () {
	console.log('setTimeout');
}, 0);

async1();

new Promise(function (resolve) {
	console.log('promise1');
	resolve(2);
}).then(function () {
	console.log('promise2');
});
console.log('script end');
export {};

分析可以得出

  • 第一轮

    • 宏任务:script start->async1 start->async2->promise1->script end,到此本次tick中宏任务结束
    • 微任务:async1 end->promise2
  • 第二轮

    • 宏任务:setTimeout

    • 微任务:无

Q2:Await/Async是怎样对待微任务的?

首先让我们从几个简单的例子去理解

  • async 返回非Promise

    ts 复制代码
    async function asyncFn() {
        return '1';
    }

    我们知道async返回的是一个Promise,如果async关键字函数返回的不是promise,会自动用Promise.resolve()包装,与之对应的Promise写法如下

    ts 复制代码
    function asyncFn() {
        return Promise.resolve('1');
    }
  • async 返回Promise

    ts 复制代码
    async function asyncFn() {
    	return new Promise((_res, _rej) => {
    		_res(1);
    	});
    }

    如果async返回的是一个Promise,不会采用Promise.resolve()去包装,而是采用new Promise((_res,_rej)=>(_res()))来包装,这里涉及到两者的区别(也就是Q4)

    ts 复制代码
    function asyncFn() {
    	return new Promise((_res2, _rej2) => {
    		_res2(
    			new Promise((_res, _rej) => {
    				_res(1);
    			})
    		);
    	});
    }
  • await 一个非Promise

    ts 复制代码
    async function async1(){
      console.log(1);
      await 1;
      console.log(2);
    }

    await后面如果是非Promise,那么就直接返回对应的值;不管await后面跟着的是什么,awiat都会阻塞后面的代码

    ts 复制代码
    function async1() {
    	console.log(1);
    	return Promise.resolve(1).then(() => {
    		//await后面的代码会被放入微任务队列
    		console.log(2);
    	});
    }
  • await Promise

    ts 复制代码
    async function async1(){
      console.log(1);
      await new Promise((_res,_rej)=>_res(1));
      console.log(2);
    }

    如果await后面是一个Promise,则这个Promise对象的终态会触发后续代码的执行。换句话说await 语句之后的代码是await的这个Promise对象的then逻辑

    这里在老版本Chrome中可能会不一致,原因是新版中进行了激进优化await v 在语义上将等价于 Promise.resolve(v),而不再是老版本的new Promise(resolve => resolve(v))

    ts 复制代码
    function async1(){
        console.log(1);
        return Promise.resolve(new Promise((_res,_rej)=>_res(1))).then(()=>{
            console.log(2);
        })
    }

示例二

ts 复制代码
Promise.resolve()
	.then(() => {
		console.log(0);
		return Promise.resolve(4);
	})
	.then(res => {
		console.log(res);
	});

Promise.resolve()
	.then(() => {
		console.log(1);
	})
	.then(() => {
		console.log(2);
	})
	.then(() => {
		console.log(3);
	})
	.then(() => {
		console.log(5);
	})
	.then(() => {
		console.log(6);
	});
//处理程序(then的callback)里返回 thenable 对象就会导致增加两个任务入列。

这里就不阐述打印顺序了,主要的问题是下面这个问题

Q3:then回调函数如果返回一个Promise/thenable,会造成什么影响吗

首先,先说做题的结论:如果返回一个Promise实例,则会多2次微任务,如果返回thenable,则多一次微任务

  • 如果返回一个Promie实例

    如果返回的是一个Promise实例,则底层会调用一个NewPromiseResolveThenableJobTask 函数去创建一个微任务PromiseResolveThenableJob,这个微任务的内容是去调用这个实例的then方法,用于将实例的onFuifilled返回值传递给外层的Promise(这里不理解可以翻到最后有参考链接)

    ts 复制代码
    Promise.resolve()
    	.then(() => {
    		console.log(0);
    		return Promise.resolve(4);
    	})
    	.then(res => {
    		console.log(res);
    	});

    例如这个例子,将Promise.resolve()返回的Promise记为p0,then(() => {console.log(0);return Promise.resolve(4);})返回的Promise记为p1,将Promise.resolve(4)返回的Promise记为p2.首先p1的onfulfilled为一个Promise,这时候会执行ResolvePromise Enqueue 代码块,里面会调用 NewPromiseResolveThenableJobTask 产生一个微任务,这个微任务的作用就是调用p2的then方法,然后在then的回调中执行p1的resolve函数,将值(4)传递给p1,大致像下面这样

    ts 复制代码
    let promiseResolveThenableJobTask = () => {
        p2.then((value) => { 
            ReslovePromise(p1, value) 
        })
    }

    注意:promiseResolveThenableJobTask本身也是一个微任务

    如果改动一下

    ts 复制代码
    Promise.resolve()
        .then(() => {
        console.log(0);
        return Promise.resolve(4).then(res=>{
            console.log('新加的')
        }).then(res=>{
            console.log('新加的2')
        })
    })
        .then(res => {
        console.log(res);//这里会变为undefined,因为他的res是'新加的2'对于的then返回的值
    });
    Promise.resolve().then(() => {
    		console.log(1);
    	}).then(() => {
    		console.log(2);
    	}).then(() => {
    		console.log(3);
    	}).then(() => {
    		console.log(4);
    	}).then(() => {
    		console.log(5);
    	}).then(() => {
    		console.log(6);
    	});

    这个新增加的then里面的log会在什么时候打印呢,当我们执行到这个第一个then(新加的)的时候,它对应的promise已经为终态了,按正常逻辑走就行,所以会直接加入微任务队列,会在0,1之后打印新加的,然后打印2,再打印新加的2,这时,新加的2所在的then处理函数就该返回一个Promise了,返回的Promise按照上面NewPromiseResolveThenableJobTask的流程走,会隔2个微任务,也就是继续打印3,4,然后再打印undefined

  • 如果返回一个thenable

    thenable:thenable对象指的是具有then方法的对象thenable = {then: function (resolve, reject) {resolve(42);}}; (注意到这里,这里再webWorker那篇文章也会用到)

    依旧会被创建一个微任务PromiseResolveThenableJob,但是和Promise不同的是,在执行这个Job时,会被直接fulfilled,少了调用then的处理,所以只延时1个时序

示例三

ts 复制代码
async function async1() {
	console.log('async1 start');
	await async2();
	console.log('async1 end');
}
async function async2() {
	console.log('async2');
	return Promise.resolve(1);
}

console.log('script start');

setTimeout(function () {
	console.log('setTimeout');
}, 0);

async1();

new Promise(function (resolve) {
	console.log('promise1');
	resolve(2);
})
	.then(function () {
		console.log('1');
	})
	.then(function () {
		console.log('2');
	})
	.then(function () {
		console.log('3');
	})
	.then(function () {
		console.log('4');
	});
console.log('script end');
export {};
ts 复制代码
(async function () {
	return Promise.resolve(1);
})().then(() => {
	console.log(1);
});
new Promise(resolve => {
	resolve(2);
})
	.then(() => {
		console.log(2);
	})
	.then(() => {
		console.log(3);
	});

这里也不分析阐述打印顺序了,主要是下面的一个问题,这里可以和示例一相互比较

Q4:Promise.resolve()new Promise((_res,_rej)=>(_res()))有什么不同?

首先给出结论,它两完全不是同一个东西

  • Promise.resolve会看传入参数是否是 Promise 实例。如果是则立刻返回传入参数本身。否则,根据自己的 this(一般用法就是全局变量 Promise)调用 new this((resolve, reject)=>...) 创建新的 Promise-like 对象,也就是说它并不会造成PromiseResolveThenableJob的创建
  • new Promise((_res,_rej)=>(_res())),而这个是Promise Resolve Function,他会看onfulfill的返回值是否是thenable,如果是那么就会入列一个新任务PromiseResolveThenableJob,这个任务之后就会调用thenable的then,注意非 pending 是立刻入列的,否则又要等 resolve/reject 时才真的创建任务入列,这句话将在Q5使用

现在反过来看async function 和 then 处理函数整体的返回值(promise)

  • 首先给出一个 async function 的例子

    ts 复制代码
    async function async1() {
    	await async2();
    	console.log('async1 end');
    }
    async function async2() {
    	console.log('async2');
    	return Promise.resolve(1);
    }
    async function async3() {
    	console.log('async2');
    }
    async1();
    Promise.resolve()
    	.then(() => {
    		console.log(1);
    	})
    	.then(() => {
    		console.log(2);
    	})
    	.then(() => {
    		console.log(3);
    	})
    	.then(() => {
    		console.log(5);
    	})
    	.then(() => {
    		console.log(6);
    	});

    执行async3和async2会得到不同的顺序,其中相差2个时序,表明async显式的返回一个Promise和隐式的返回一个Promise是有不同的

    执行async2

    执行async3

    接着,如果async都是统一将返回值用Promise.resolve()来包裹,那么我们将async2进行转换一下

    ts 复制代码
    // async function async2() {
    // 	console.log('async2');
    // 	return Promise.resolve(1);
    // }
    function async2() {
    	console.log('async2');
    	return Promise.resolve(Promise.resolve(1));
    }

    我们再来执行以下async2

    会发现和原本的执行顺序不同,并且和async3的执行顺序相同,接着我们用new Promise的方式返回Promise实例

    ts 复制代码
    // async function async2() {
    // 	console.log('async2');
    // 	return Promise.resolve(1);
    // }
    function async2() {
    	console.log('async2');
    	return Promise.resolve(Promise.resolve(1));
    }

    看一下执行顺序,会发现和原本的async2是一致的(本篇文章中说的等价或一致都是指的执行顺序,至于实际是否底层一致并不在讨论范围之内)

    所以我们可以知道,对于async函数显式的返回一个thenable,他是使用new Promise的方式去包裹返回值的,对于非thenable,则使用Promise.resolve()包裹

  • then 处理函数整体的返回值(promise)

    这里我直接甩李杭帆大佬对于then返回promise之后的处理原话

    then 创建新 Promise 实例,其中一个子步骤相当于调用 new Promise((resolve, reject)=>...)

    利用new Promise创建一个Promise实例,刚好也和我们的给出的结论对上了,同时也是对Q3的补充

示例四

ts 复制代码
async function async1() {
	console.log('async1 start');
	// const async2Promise = async2();
	// const awaitPromise = Promise.resolve(async2Promise);
	// console.log(awaitPromise === async2Promise); //true
	await async2();
	console.log('async1 end');
}
async function async2() {
	console.log('async2');
	return new Promise((res, rej) => {
		res(
			new Promise((res, rej) => {
				res(
					new Promise((res, rej) => {
						res(1);
					})
				);
			})
		);
	});
}

async1();

new Promise(function (resolve) {
	console.log('promise1');
	resolve(2);
})
	.then(function p1fn() {
		console.log('1');
	})
	.then(function p2fn() {
		console.log('2');
	})
	.then(function p3fn() {
		console.log('3');
	})
	.then(function p4fn() {
		console.log('4');
	})
	.then(function p4fn() {
		console.log('5');
	});

我们来分析一下:

  • 第一轮:

    • 宏任务:
      • 执行async1,打印async1 start,执行async2,打印async2,发现3个Promise,(为了便于描述,将最里层Promise记为p0,往外依次为p1,p2,由于显式返回Promise,所以async返回会额外套一个Promise(记为p4))
      • 按照代码顺序依次执行,首先p0进行了res(1),p0执行完毕,p1的res有了结果,res了一个promise,那么开启额外的微任务PromiseResolveThenableJob(记为PromiseResolveThenableJob1),并将其加入到微任务队列,此时微任务队列为[PromiseResolveThenableJob1]
      • 继续执行,p1执行完毕,p2的res有了结果(这个结果不是说确定了终态),res了一个promise,那么开启微任务PromiseResolveThenableJob(记为PromiseResolveThenableJob2),并将其加入到微任务队列中,此时微任务队列为[PromiseResolveThenableJob1,PromiseResolveThenableJob2]
      • 继续执行,p2执行完毕,async2函数执行完毕,返回值明确,p3的res有了结果(这个结果不是说确定了终态),res了一个promise,那么开启微任务PromiseResolveThenableJob(记为PromiseResolveThenableJob3),并将其加入到微任务队列中,此时微任务队列为[PromiseResolveThenableJob1,PromiseResolveThenableJob2,PromiseResolveThenableJob3]
      • 继续执行,await右边的Promise明确,阻塞后边代码,将其放入到Promise.resolve(p3).then(()=>{阻塞代码}),由于Promise.resolve一个Promise会直接返回这个promise,所以也就是p3.then(()=>{阻塞代码}),由于p3状态未确定,所以先加入缓存队列,async1执行完毕,此时微任务队列为[PromiseResolveThenableJob1,PromiseResolveThenableJob2,PromiseResolveThenableJob3]
      • 继续执行(后面5个数字分别对应的Promise为p_1、p_2、p_3、p_4、p_5),打印promise1,并将最外层Promise(p_1)变为终态,p1fn加入微任务队列,此时微任务队列为[PromiseResolveThenableJob1,PromiseResolveThenableJob2,PromiseResolveThenableJob3,p1fn]
      • 继续执行,后续三个都由于其对应的promise终态未确定,所以先放入缓存队列,到此第一轮宏任务执行完毕
    • 微任务:
      • 取出PromiseResolveThenableJob1,由于p0终态已经确定,p0.then的处理函数(后续都简称为p(0|1|2|3).then)直接加入微任务队列,此时微任务队列为[PromiseResolveThenableJob2,PromiseResolveThenableJob3,p1fn,p0.then]
      • 取出PromiseResolveThenableJob2,执行,由于p1的终态还未确定(等到p0.then执行完毕才能确定),所以先放入缓存队列,此时微任务队列为[PromiseResolveThenableJob3,p1fn,p0.then]
      • 取出PromiseResolveThenableJob3,执行,由于p2的终态还未确定(等到p1.then执行完毕才能确定),所以先放入缓存队列,此时微任务队列为[p1fn,p0.then]
      • 取出p1fn执行,打印1,执行完毕,p_2终态确定,p2fn加入微任务队列,此时微任务队列为[p0.then,p2fn]
      • 取出p0.then执行,p1终态确定,p1.then放入微任务队列,此时微任务队列为[p2fn,p1.then]
      • 取出p2fn执行,打印2,执行完毕,p_3终态确定,p3fn加入微任务队列,此时微任务队列为[p1.then,p3fn]
      • 取出p1.then执行,p2终态确定,p2.then放入微任务队列,此时微任务队列为[p3fn,p2.then]
      • 取出p3fn执行,打印3,执行完毕,p_4终态确定,p4fn加入微任务队列,此时微任务队列为[p2.then,p4fn]
      • 取出p2.then执行,p3终态确定,p3.then放入微任务队列,此时微任务队列为[p4fn,p3.then]
      • 取出p4fn执行,打印4,执行完毕,p_5终态确定,p5fn加入微任务队列,此时微任务队列为[p3.then,p5fn]
      • 取出p3.then执行,阻塞的代码开始执行,打印async1 end,执行完毕,此时微任务队列为[p5fn]
      • 取出p5fn执行,打印5,执行完毕,此时微任务队列清空,第一轮事件循环执行完毕
  • 总打印顺序:

    async1 start->async2->promise1->1->2->3->4->async1 end->5

Q5:在Promie中'递归'resolve(Promise)会怎样?

首先记得上文提到了一句话非 pending 是立刻入列的,否则又要等 resolve/reject 时才真的创建任务入列

  • 规则一:对于async函数来说,如果显式返回一个Promise,那么会用new Promise包裹

  • 规则二:使用 new promise 去resolve一个thenable会创建额外的微任务PromiseResolveThenableJob,微任务中会调用内层promise的then去实现值的传递

  • 规则三:如果调用then时,promise不是终态,会放入缓存队列中,直到变为终态才放入微任务队列中

  • 规则四:如果使用Promise.resolve一个Promise实例,立刻返回其Promise实例本身

  • 规则五:await一个Promise,等价于Promise.resolve(Promise)

    结合上面5条规则,再看下执行顺序分析应该就没问题了(有点规则怪谈的感觉?)

nextTick+Promise(node)

研究node时会继续补充,这部分会涉及到微任务队列的优先级,一类微任务队列执行完毕之后,才能执行下一类微任务队列,先nextTick类微任务队列,再promise类微任务队列

可以先对比看看这2个示例,为什么前面的示例nextTick会在最后打印,而后面的示例可以插队?

ts 复制代码
new Promise((resolve, reject) => {
	console.log(1);
	resolve(2);
})
	.then(a => {
		process.nextTick(() => {
			console.log('我是最后打印');
		});
		console.log(2);
		new Promise((resolve, reject) => {
			//内层Promise
			console.log(3);
			resolve(2);
		})
			.then(c => {
				console.log(4);
			})
			.then(d => {
				console.log(6);
			});
	})
	.then(b => {
		console.log(5);
	});
ts 复制代码
process.nextTick(function(){
    console.log(7);
});

new Promise(function(resolve){
    console.log(3);
    resolve();
    console.log(4);
}).then(function(){
    console.log(5);
});

process.nextTick(function(){
    console.log(8);
});

参考链接

promise.then 中 return Promise.resolve 后,发生了什么? - 知乎 (zhihu.com)

javascript - promise then 的回调函数是在什么时候进入微任务队列的? - SegmentFault 思否

JS-ES6-Promise/Promise A+规范 · Issue #112 · yaofly2012/note (github.com)

令人费解的 async/await 执行顺序 - 掘金 (juejin.cn)

理解 JavaScript 的 async/await - 边城客栈 - SegmentFault 思否

【V8源码补充篇】从一道让我失眠的 Promise 面试题开始,深入分析 Promise 实现细节 - 掘金 (juejin.cn)

javascript - async/await只是then 的语法糖吗?有什么细微上的不同吗?为什么这两段代码输出不一致? - SegmentFault 思否(@普拉斯强的回答我验证了一下,可能是由于激进优化的原因从而在新版本存在一些问题?)

相关推荐
Dread_lxy8 分钟前
vue 依赖注入(Provide、Inject )和混入(mixins)
前端·javascript·vue.js
奔跑草-1 小时前
【前端】深入浅出 - TypeScript 的详细讲解
前端·javascript·react.js·typescript
羡与1 小时前
echarts-gl 3D柱状图配置
前端·javascript·echarts
前端郭德纲1 小时前
浏览器是加载ES6模块的?
javascript·算法
JerryXZR2 小时前
JavaScript核心编程 - 原型链 作用域 与 执行上下文
开发语言·javascript·原型模式
帅帅哥的兜兜2 小时前
CSS:导航栏三角箭头
javascript·css3
渗透测试老鸟-九青2 小时前
通过投毒Bingbot索引挖掘必应中的存储型XSS
服务器·前端·javascript·安全·web安全·缓存·xss
龙猫蓝图2 小时前
vue el-date-picker 日期选择器禁用失效问题
前端·javascript·vue.js
夜色呦2 小时前
掌握ECMAScript模块化:构建高效JavaScript应用
前端·javascript·ecmascript
peachSoda72 小时前
随手记:简单实现纯前端文件导出(XLSX)
前端·javascript·vue.js