Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

第 8 题:setTimeout、Promise、Async/Await 的区别 #33

Open
baoyipin opened this issue Mar 8, 2019 · 33 comments
Open

第 8 题:setTimeout、Promise、Async/Await 的区别 #33

baoyipin opened this issue Mar 8, 2019 · 33 comments
Labels

Comments

@baoyipin
Copy link

baoyipin commented Mar 8, 2019

这题怎么没人答,我说下我粗浅的认识,抛砖引玉,欢迎指正和补充。
我觉得这题主要是考察这三者在事件循环中的区别,事件循环中分为宏任务队列和微任务队列。
其中settimeout的回调函数放到宏任务队列里,等到执行栈清空以后执行;
promise.then里的回调函数会放到相应宏任务的微任务队列里,等宏任务里面的同步代码执行完再执行;async函数表示函数里面可能会有异步方法,await后面跟一个表达式,async方法执行时,遇到await会立即执行表达式,然后把表达式后面的代码放到微任务队列里,让出执行栈让同步代码先执行。

@JJL-SH
Copy link

JJL-SH commented Mar 11, 2019

宏观任务队列
微观任务队列
的区别

@sisterAn
Copy link
Collaborator

sisterAn commented Mar 11, 2019

1. setTimeout

console.log('script start')	//1. 打印 script start
setTimeout(function(){
    console.log('settimeout')	// 4. 打印 settimeout
})	// 2. 调用 setTimeout 函数,并定义其完成后执行的回调函数
console.log('script end')	//3. 打印 script start
// 输出顺序:script start->script end->settimeout

2. Promise

Promise本身是同步的立即执行函数, 当在executor中执行resolve或者reject的时候, 此时是异步操作, 会先执行then/catch等,当主栈完成后,才会去调用resolve/reject中存放的方法执行,打印p的时候,是打印的返回结果,一个Promise实例。

console.log('script start')
let promise1 = new Promise(function (resolve) {
    console.log('promise1')
    resolve()
    console.log('promise1 end')
}).then(function () {
    console.log('promise2')
})
setTimeout(function(){
    console.log('settimeout')
})
console.log('script end')
// 输出顺序: script start->promise1->promise1 end->script end->promise2->settimeout

当JS主线程执行到Promise对象时,

  • promise1.then() 的回调就是一个 task

  • promise1 是 resolved或rejected: 那这个 task 就会放入当前事件循环回合的 microtask queue

  • promise1 是 pending: 这个 task 就会放入 事件循环的未来的某个(可能下一个)回合的 microtask queue 中

  • setTimeout 的回调也是个 task ,它会被放入 macrotask queue 即使是 0ms 的情况

3. async/await

async function async1(){
   console.log('async1 start');
    await async2();
    console.log('async1 end')
}
async function async2(){
    console.log('async2')
}

console.log('script start');
async1();
console.log('script end')

// 输出顺序:script start->async1 start->async2->script end->async1 end

async 函数返回一个 Promise 对象,当函数执行的时候,一旦遇到 await 就会先返回,等到触发的异步操作完成,再执行函数体内后面的语句。可以理解为,是让出了线程,跳出了 async 函数体。

举个例子:

async function func1() {
    return 1
}

console.log(func1())

在这里插入图片描述
很显然,func1的运行结果其实就是一个Promise对象。因此我们也可以使用then来处理后续逻辑。

func1().then(res => {
    console.log(res);  // 30
})

await的含义为等待,也就是 async 函数需要等待await后的函数执行完成并且有了返回结果(Promise对象)之后,才能继续执行下面的代码。await通过返回一个Promise对象来实现同步的效果。

更多可见setTimeout、Promise、Async/Await

@Fengziyin1234
Copy link

Fengziyin1234 commented Mar 29, 2019

上面的解释都很详细了。

我拿 babel es8 编译了下 async/await 结果是这样的

async function asyncTest() {
  const ret = await asyncFunction();
}

--->

function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
  try {
    var info = gen[key](arg);
    var value = info.value;
  } catch (error) {
    reject(error);
    return;
  }
  if (info.done) {
    resolve(value);
  } else {
    Promise.resolve(value).then(_next, _throw);
  }
}

function _asyncToGenerator(fn) {
  return function() {
    var self = this,
      args = arguments;
    return new Promise(function(resolve, reject) {
      var gen = fn.apply(self, args);
      function _next(value) {
        asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
      }
      function _throw(err) {
        asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
      }
      _next(undefined);
    });
  };
}

function asyncTest() {
  return _asyncTest.apply(this, arguments);
}

function _asyncTest() {
  _asyncTest = _asyncToGenerator(function*() {
    const ret = yield asyncFunction();
  });
  return _asyncTest.apply(this, arguments);
}

await/async 是通过 Generator/function* 来实现的。 所以 async/await 的相关优势也来自于generator。 Generator 是一个可以暂停 function ,感觉推出 generator 的目的包括不仅限于 Callback Hell 和 Inversion of Control。 感觉整个 community 有在往那个方向走。

function* generator(i) {
  console.log('inside before')
  yield i;
  yield i + 10;
  console.log('inside after')
}

var gen = generator(10);
console.log('outside before')
console.log(gen.next().value);
console.log(gen.next().value);
console.log('outside after')
gen.next();

结果如下

> "outside before"
> "inside before"
> 10
> 20
> "outside after"
> "inside after" // 如果不加最后一个gen.next(); 就不会有这一行

@MrRENGE
Copy link

MrRENGE commented Apr 6, 2019

1. setTimeout

console.log('script start')	//1. 打印 script start
setTimeout(function(){
    console.log('settimeout')	// 4. 打印 settimeout
})	// 2. 调用 setTimeout 函数,并定义其完成后执行的回调函数
console.log('script end')	//3. 打印 script start
// 输出顺序:script start->script end->settimeout

2. Promise

Promise本身是同步的立即执行函数, 当在executor中执行resolve或者reject的时候, 此时是异步操作, 会先执行then/catch等,当主栈完成后,才会去调用resolve/reject中存放的方法执行,打印p的时候,是打印的返回结果,一个Promise实例。

console.log('script start')
let promise1 = new Promise(function (resolve) {
    console.log('promise1')
    resolve()
    console.log('promise1 end')
}).then(function () {
    console.log('promise2')
})
setTimeout(function(){
    console.log('settimeout')
})
console.log('script end')
// 输出顺序: script start->promise1->promise1 end->script end->promise2->settimeout

当JS主线程执行到Promise对象时,

  • promise1.then() 的回调就是一个 task
  • promise1 是 resolved或rejected: 那这个 task 就会放入当前事件循环回合的 microtask queue
  • promise1 是 pending: 这个 task 就会放入 事件循环的未来的某个(可能下一个)回合的 microtask queue 中
  • setTimeout 的回调也是个 task ,它会被放入 macrotask queue 即使是 0ms 的情况

3. async/await

async function async1(){
   console.log('async1 start');
    await async2();
    console.log('async1 end')
}
async function async2(){
    console.log('async2')
}

console.log('script start');
async1();
console.log('script end')

// 输出顺序:script start->async1 start->async2->script end->async1 end

async 函数返回一个 Promise 对象,当函数执行的时候,一旦遇到 await 就会先返回,等到触发的异步操作完成,再执行函数体内后面的语句。可以理解为,是让出了线程,跳出了 async 函数体。

举个例子:

async function func1() {
    return 1
}

console.log(func1())

在这里插入图片描述
很显然,func1的运行结果其实就是一个Promise对象。因此我们也可以使用then来处理后续逻辑。

func1().then(res => {
    console.log(res);  // 30
})

await的含义为等待,也就是 async 函数需要等待await后的函数执行完成并且有了返回结果(Promise对象)之后,才能继续执行下面的代码。await通过返回一个Promise对象来实现同步的效果。

更多可见setTimeout、Promise、Async/Await
什么叫同步的立即执行函数?建议大佬看看规范中Promise的定义。

@yygmind yygmind changed the title 第八题:setTimeout、Promise、Async/Await 的区别 第8题:setTimeout、Promise、Async/Await 的区别 Apr 26, 2019
@yygmind yygmind changed the title 第8题:setTimeout、Promise、Async/Await 的区别 第 8 题:setTimeout、Promise、Async/Await 的区别 Apr 26, 2019
@song-le-yi
Copy link

有个问题,如果promise的finally方法里面出错了怎么办呀,怎么捕获呀?

@MrRENGE
Copy link

MrRENGE commented Jul 16, 2019

有个问题,如果promise的finally方法里面出错了怎么办呀,怎么捕获呀?

会返回一个 被reject 的promise对象。继续绑定catch函数可以捕获到

@xuetupeng
Copy link

async1 start

写的很好,感谢大大的分享,我还有一个疑惑上面提到的Async/Await await执行的代码 ,会跳出线程,但是await上面的打印会出现在await执行打印的前面,意思是await前面的代码都会跳出线程(异步执行),只有await后面的代码才是同步执行这个意思吗?

@rzh11111111
Copy link

宏观微观?我记得promise的执行是在setimeout之前(好像),async/await是基于promise的

@xuetupeng
Copy link

宏观微观?我记得promise的执行是在setimeout之前(好像),async/await是基于promise的

宏观吧,我其实就是比较奇怪,await前面的代码都跳出了线程吗,还是单纯就await 后面的执行跳出了线程, 也可能是我表达有问题, 为啥await以及前面代码的都会先打印 然后才是跳出线程执行外面的,最后才执行await下面的代码。

@rzh11111111
Copy link

宏观微观?我记得承诺的执行是在setimeout之前(好像),异步/ AWAIT是基于承诺的

宏观吧,我其实就是比较奇怪,等待前面的代码都跳出了线程吗,还是单纯就等待后面的执行跳出了线程,也可能是我表达有问题,为啥等待以及前面代码的都会先打印然后才是跳出线程执行外面的,最后才执行的await下面的代码。

就像setimeout是1,2,3这样的顺序来的,但是promise是1.1,1.2,promise是微观的,async,await基于promise也大概和promise一样

@xuetupeng
Copy link

宏观微观?我记得承诺的执行是在setimeout之前(好像),异步/ AWAIT是基于承诺的

宏观吧,我其实就是比较奇怪,等待前面的代码都跳出了线程吗,还是单纯就等待后面的执行跳出了线程,也可能是我表达有问题,为啥等待以及前面代码的都会先打印然后才是跳出线程执行外面的,最后才执行的await下面的代码。

就像setimeout是1,2,3这样的顺序来的,但是promise是1.1,1.2,promise是微观的,async,await基于promise也大概和promise一样
恩,这个其实是了解的,就是Async/Await的有点没吃透,Await前面的语句 和Await后面的语句,两者间的差异, 就是为啥不是await 的内容 先输出 然后await上面和下面再输出

@rzh11111111
Copy link

宏观微观?我记得承诺的执行是在setimeout之前(好像),异步/ AWAIT是基于承诺的

宏观吧,我其实就是比较奇怪,等待前面的代码都跳出了线程吗,还是单纯就等待后面的执行跳出了线程,也可能是我表达有问题,为啥等待以及前面代码的都会先打印然后才是跳出线程执行外面的,最后才执行的await下面的代码。

就像setimeout是1,2,3这样的顺序来的,但是promise是1.1,1.2,promise是微观的,async,await基于promise也大概和promise一样
恩,这个其实是了解的,就是Async/Await的有点没吃透,Await前面的语句 和Await后面的语句,两者间的差异, 就是为啥不是await 的内容 先输出 然后await上面和下面再输出

async/await不是同步了嘛,我也缕的不是很清楚,第10题那个回答挺好的

@FoooooooF
Copy link

FoooooooF commented Aug 2, 2019

2. 基础知识

需要先看懂这两份资料,他们会让你构建一个完整的从 上下文执行栈,Event Loop,任务队列(task queue),再到Microtask(微任务)、Macrotask/Task(宏任务)知识体系。看完这个来解决一些setTimeout,pormsie,async 的执行先后问题,简直都是毛毛雨!

js运行原理

首先补齐基础,来看一下js 引擎(如:V8)的运行原理,这位Philip Roberts小哥讲的非常好,运行过程都使用动画展现,过程非常生动,条理也很清楚,当然ppt也做的不错。
这是B站上带英文字幕的版本
视频地址

这个是核心思想的截图
evnet loop

Microtask、Macrotask/Task

Philip Roberts视频中缺少了任务队列(task queue)区分为Microtask(微任务)、Macrotask/Task(宏任务)的部分,这里需要看第二份资料,详细的介绍了Microtask、Macrotask/Task 的运行过程,且分析了浏览器的执行差异,Jake Archibald英文博客地址

看博客注意事项

  • 博客内有带执行步骤的动画,一定要亲自点一下:star:

  • 博客中将Macrotask叫做Task;

  • 分析浏览器差异的部分基本可以略过了,随着版本更新,这些差异基本都被修补了。我们只要看博客中关于chrome浏览器的正确输出结果就可以了

Microtask和Macrotask所包含的api:

Microtask
- process.nextTick
- promise
- Object.observe (废弃)
- MutationObserver

Macrotask
- setTimeout
- setImmediate
- setInterval
- I/O
- UI 渲染

如果不想看英文博客,我在这里作一点简单的总结:

  1. 在执行上下文栈的同步任务执行完后;
  2. 首先执行Microtask队列,按照队列先进先出的原则,一次执行完所有Microtask队列任务;
  3. 然后执行Macrotask/Task队列,一次执行一个,一个执行完后,检测 Microtask是否为空;
  4. 为空则执行下一个Macrotask/Task;
  5. 不为空则执行Microtask

更多内容可以看我的github

@xuanbabybaby
Copy link

宏观微观?我记得承诺的执行是在setimeout之前(好像),异步/ AWAIT是基于承诺的

宏观吧,我其实就是比较奇怪,等待前面的代码都跳出了线程吗,还是单纯就等待后面的执行跳出了线程,也可能是我表达有问题,为啥等待以及前面代码的都会先打印然后才是跳出线程执行外面的,最后才执行的await下面的代码。

就像setimeout是1,2,3这样的顺序来的,但是promise是1.1,1.2,promise是微观的,async,await基于promise也大概和promise一样
恩,这个其实是了解的,就是Async/Await的有点没吃透,Await前面的语句 和Await后面的语句,两者间的差异, 就是为啥不是await 的内容 先输出 然后await上面和下面再输出

几乎同样的疑问,,,我搞不懂为什么await下面的代码会在script end之后输出,,我以为是在async2执行完就会执行的 然后才执行 script end,,,,不太懂

@rosefang
Copy link

rosefang commented Aug 24, 2019

2. 基础知识

需要先看懂这两份资料,他们会让你构建一个完整的从 上下文执行栈,Event Loop,任务队列(task queue),再到Microtask(微任务)、Macrotask/Task(宏任务)知识体系。看完这个来解决一些setTimeout,pormsie,async 的执行先后问题,简直都是毛毛雨!

js运行原理

首先补齐基础,来看一下js 引擎(如:V8)的运行原理,这位Philip Roberts小哥讲的非常好,运行过程都使用动画展现,过程非常生动,条理也很清楚,当然ppt也做的不错。
这是B站上带英文字幕的版本
视频地址

这个是核心思想的截图
evnet loop

Microtask、Macrotask/Task

Philip Roberts视频中缺少了任务队列(task queue)区分为Microtask(微任务)、Macrotask/Task(宏任务)的部分,这里需要看第二份资料,详细的介绍了Microtask、Macrotask/Task 的运行过程,且分析了浏览器的执行差异,Jake Archibald英文博客地址

看博客注意事项

  • 博客内有带执行步骤的动画,一定要亲自点一下star
  • 博客中将Macrotask叫做Task;
  • 分析浏览器差异的部分基本可以略过了,随着版本更新,这些差异基本都被修补了。我们只要看博客中关于chrome浏览器的正确输出结果就可以了

Microtask和Macrotask所包含的api:

Microtask
- process.nextTick
- promise
- Object.observe (废弃)
- MutationObserver

Macrotask
- setTimeout
- setImmediate
- setInterval
- I/O
- UI 渲染

如果不想看英文博客,我在这里作一点简单的总结:

  1. 在执行上下文栈的同步任务执行完后;
  2. 首先执行Microtask队列,按照队列先进先出的原则,一次执行完所有Microtask队列任务;
  3. 然后执行Macrotask/Task队列,一次执行一个,一个执行完后,检测 Microtask是否为空;
  4. 为空则执行下一个Macrotask/Task;
  5. 不为空则执行Microtask

更多内容可以看我的github

image
你好,这里的例子是写错了吧,实际运行不是这样的结果呢

@FoooooooF
Copy link

文章中的例子确实写错了

2. 基础知识

需要先看懂这两份资料,他们会让你构建一个完整的从 上下文执行栈,Event Loop,任务队列(task queue),再到Microtask(微任务)、Macrotask/Task(宏任务)知识体系。看完这个来解决一些setTimeout,pormsie,async 的执行先后问题,简直都是毛毛雨!

js运行原理

首先补齐基础,来看一下js 引擎(如:V8)的运行原理,这位Philip Roberts小哥讲的非常好,运行过程都使用动画展现,过程非常生动,条理也很清楚,当然ppt也做的不错。
这是B站上带英文字幕的版本
视频地址
这个是核心思想的截图
evnet loop

Microtask、Macrotask/Task

Philip Roberts视频中缺少了任务队列(task queue)区分为Microtask(微任务)、Macrotask/Task(宏任务)的部分,这里需要看第二份资料,详细的介绍了Microtask、Macrotask/Task 的运行过程,且分析了浏览器的执行差异,Jake Archibald英文博客地址
看博客注意事项

  • 博客内有带执行步骤的动画,一定要亲自点一下star
  • 博客中将Macrotask叫做Task;
  • 分析浏览器差异的部分基本可以略过了,随着版本更新,这些差异基本都被修补了。我们只要看博客中关于chrome浏览器的正确输出结果就可以了

Microtask和Macrotask所包含的api:

Microtask
- process.nextTick
- promise
- Object.observe (废弃)
- MutationObserver

Macrotask
- setTimeout
- setImmediate
- setInterval
- I/O
- UI 渲染

如果不想看英文博客,我在这里作一点简单的总结:

  1. 在执行上下文栈的同步任务执行完后;
  2. 首先执行Microtask队列,按照队列先进先出的原则,一次执行完所有Microtask队列任务;
  3. 然后执行Macrotask/Task队列,一次执行一个,一个执行完后,检测 Microtask是否为空;
  4. 为空则执行下一个Macrotask/Task;
  5. 不为空则执行Microtask

更多内容可以看我的github

image
你好,这里的例子是写错了吧,实际运行不是这样的结果呢

文章中的的结果确实写错了,现在已经更正.谢谢你的指正! 如果对你造成误导,非常抱歉!

@qylbj2016
Copy link

这题怎么没人答,我说下我粗浅的认识,抛砖引玉,欢迎指正和补充。
我觉得这题主要是考察这三者在事件循环中的区别,事件循环中分为宏任务队列和微任务队列。
其中settimeout的回调函数放到宏任务队列里,等到执行栈清空以后执行;
promise.then里的回调函数会放到相应宏任务的微任务队列里,等宏任务里面的同步代码执行完再执行;async函数表示函数里面可能会有异步方法,await后面跟一个表达式,async方法执行时,遇到await会立即执行表达式,然后把表达式后面的代码放到微任务队列里,让出执行栈让同步代码先执行。

第一次执行栈的同步任务都完成后,接着处理的应该是微任务吧,然后再从宏任务队列里拿一条宏任务到执行栈中,等执行栈中的宏任务处理完,再去清空微任务队列。

@gaozf
Copy link

gaozf commented Sep 1, 2019

               async function async1() {
			await async2();
			console.log('async1 end');
		}

		async function async2() {
			return 'async2';
			// return Promise.resolve('async2');
		}

		async1();

		new Promise(function(resolve) {
			resolve();
		}).then(function() {
			console.log('Promise then');
		});

async函数本身返回是一个promise,为什么async2显式返回promise和不显式返回,两行打印顺序不一致,什么原理?显式返回顺序:Promise then --> async1 end;返回普通值顺序:async1 end --> Promise then

@weixiaoxu123
Copy link

setTimeout 和promise 好理解, 宏任务队列和微任务队列。,
await fun() \n balabala..
await返回一个promise 后执行 balabala 所以我的理解就像使用了.then(()=>balabala),也是进入的微任务队列。 这样执行的顺序也就明白了

@NARUTOne
Copy link

Event Loop

@tongzhenyu0312
Copy link

有个问题,如果promise的finally方法里面出错了怎么办呀,怎么捕获呀?

finally也是返回一个promise,自然可以用catch捕获运行时错误。

@yinzuowen
Copy link

yinzuowen commented Nov 27, 2019

               async function async1() {
			await async2();
			console.log('async1 end');
		}

		async function async2() {
			return 'async2';
			// return Promise.resolve('async2');
		}

		async1();

		new Promise(function(resolve) {
			resolve();
		}).then(function() {
			console.log('Promise then');
		});

async函数本身返回是一个promise,为什么async2显式返回promise和不显式返回,两行打印顺序不一致,什么原理?显式返回顺序:Promise then --> async1 end;返回普通值顺序:async1 end --> Promise then

return ‘async2’时,执行代码相当于如下:
new Promise(function (resolve) {
resolve('async2');
}).then(function() {
console.log('async1 end');
});
console.log('async1 end');排在microtask queue前面,所以输出顺序是async1 end --> Promise then

return Promise.resolve('async2')时,执行代码相当于如下:
new Promise(function (resolve) {
resolve(Promise.resolve('async2'));
}).then(function() {
console.log('async1 end');
});
此时的then取决于里层promise的状态,console.log('async1 end')会排在microtask queue后面,所以输出顺序是Promise then --> async1 end

@yygmind yygmind added the 异步 label Dec 16, 2019
@SnailOwO
Copy link

SnailOwO commented Dec 27, 2019

自己回答一波,若有错误请各位指出,互相学习!谢谢 ~
鉴于上面各位大佬回答的,其实这题主要还是考浏览器的EVENT LOOP
setTimeout属于宏任务,Promise属于微任务.而async和await其实是geneorator的语法糖,实质上最后返回的也是promise,所以我将其归为微任务(有错的,请指出,谢谢)。
而浏览器的执行顺序是在一开始会通篇扫描整个脚本,生成主执行栈,用于执行同步任务.而异步任务会加入至浏览器的任务队列中.当执行栈为空,就会去Task队列中(任务队列)取出需要执行的代码放入执行栈中去执行。而Task队列中,我们又再之前提及到分:微任务和宏任务
微任务的优先级大于宏任务,所以在执行栈为空的时候,首先会去执行Micortask(微任务)队列,执行完毕后再去取Macrotask(宏任务)队列去执行栈中执行,一次执行一个,再去检查Micortask(微任务),若存在则执行Microtask,若没有则取下一个Macrotask任务继续执行,直至为空。
以上,是我对EventLoop的回答,但是我还是想在此基础上回答下关于这三个的异步不同之处。

setTimeout

setTimeout的异步使用方法算是比较古老的回调函数方式,就是我们之前写Jquery的时候,ajax的最常见的使用方式,这种的好处在于用很简单的方式实现了异步的方式,从而解决了异步直肠子的问题(耗时任务,一直处于等待)。缺点:回调地狱,这是写了多年Jq的一直很恶心的地方,代码嵌套太多,牵一发而动全身。

Promise

优点:解决了回调函数的问题,可以使用链式结构,代码逻辑清晰。
缺点:无法取消,有时逻辑复杂then太多,then中嵌套then,错误捕获不到正确的位置,只能通过自己的catch或者写reject(erro)的回调来捕获。

async

至此,我们再次引出我们的Async/Await语法糖
优点:以同步代码的方式去写异步,代码逻辑清晰,符合我们平时写的逻辑。缺点:因为await将异步代码改造成了同步代码,如果多个异步代码没有依赖性却使用了await会导致性能上的降低。(这个是可以避免的)

@JiangMengLei
Copy link

宏观微观?我记得承诺的执行是在setimeout之前(好像),异步/ AWAIT是基于承诺的

宏观吧,我其实就是比较奇怪,等待前面的代码都跳出了线程吗,还是单纯就等待后面的执行跳出了线程,也可能是我表达有问题,为啥等待以及前面代码的都会先打印然后才是跳出线程执行外面的,最后才执行的await下面的代码。

就像setimeout是1,2,3这样的顺序来的,但是promise是1.1,1.2,promise是微观的,async,await基于promise也大概和promise一样
恩,这个其实是了解的,就是Async/Await的有点没吃透,Await前面的语句 和Await后面的语句,两者间的差异, 就是为啥不是await 的内容 先输出 然后await上面和下面再输出

async/await不是同步了嘛,我也缕的不是很清楚,第10题那个回答挺好的

引用阮一峰老师书中一句话:“ async 函数返回一个 Promise 对象,当函数执行的时候,一旦遇到 await 就会先返回,等到触发的异步操作完成,再接着执行函数体内后面的语句。”
简单的说,先去执行后面的同步任务代码,执行完成后,也就是表达式中的 Promise 解析完成后继续执行 async 函数并返回解决结果。(其实还是本轮循环promise的问题,最后的resolve属于异步,位于本轮循环的末尾。)

async1 end --> Promise then

我试了 结果一样啊  都是 async1 end --> Promise then

@lqk9511
Copy link

lqk9511 commented Feb 12, 2020

@sisterAn 你好,关于你的 3.async/await 我觉得不是很完备。

async function async1(){
   console.log('async1 start');
   // 我理解是这里的 async2 函数是返回的 Premise 对象
   // 但是 Premise 其本身就是一个宏任务 不用 await 也应该是同步执行的
    await async2();
    console.log('async1 end')
}
async function async2(){
    console.log('async2')
}

console.log('script start');
async1();
console.log('script end')

// 输出顺序:script start->async1 start->async2->script end->async1 end

而且请看一下下面例子是否能够表明清楚
或者有什么不对的地方

// 被添加入下一次宏任务队列
setTimeout(_ => console.log(6));

async function test() {
  console.log(1);
  // 我的理解是
  // 遇到 await 先执行其同步内容
  // 让出执行线程(跳出当前任务)同时推入微任务队列  继续往下执行
  // 执行完之后 回来继续执行
  await new Promise((resolve, reject) => {
    console.log(2);
    resolve();
  }).then(_ => console.log(4));
  // 这里 5 没有进入微任务队列 只不是相当于被挂起了
  console.log(5);
}

test();
test();

console.log(3);
// 如果执行一次 test 函数  结果 => 123456
// 如果执行两次 test 函数  结果 => 1212344556

@rubyisapm
Copy link

1. setTimeout

console.log('script start')	//1. 打印 script start
setTimeout(function(){
    console.log('settimeout')	// 4. 打印 settimeout
})	// 2. 调用 setTimeout 函数,并定义其完成后执行的回调函数
console.log('script end')	//3. 打印 script start
// 输出顺序:script start->script end->settimeout

2. Promise

Promise本身是同步的立即执行函数, 当在executor中执行resolve或者reject的时候, 此时是异步操作, 会先执行then/catch等,当主栈完成后,才会去调用resolve/reject中存放的方法执行,打印p的时候,是打印的返回结果,一个Promise实例。

console.log('script start')
let promise1 = new Promise(function (resolve) {
    console.log('promise1')
    resolve()
    console.log('promise1 end')
}).then(function () {
    console.log('promise2')
})
setTimeout(function(){
    console.log('settimeout')
})
console.log('script end')
// 输出顺序: script start->promise1->promise1 end->script end->promise2->settimeout

当JS主线程执行到Promise对象时,

  • promise1.then() 的回调就是一个 task
  • promise1 是 resolved或rejected: 那这个 task 就会放入当前事件循环回合的 microtask queue
  • promise1 是 pending: 这个 task 就会放入 事件循环的未来的某个(可能下一个)回合的 microtask queue 中
  • setTimeout 的回调也是个 task ,它会被放入 macrotask queue 即使是 0ms 的情况

3. async/await

async function async1(){
   console.log('async1 start');
    await async2();
    console.log('async1 end')
}
async function async2(){
    console.log('async2')
}

console.log('script start');
async1();
console.log('script end')

// 输出顺序:script start->async1 start->async2->script end->async1 end

async 函数返回一个 Promise 对象,当函数执行的时候,一旦遇到 await 就会先返回,等到触发的异步操作完成,再执行函数体内后面的语句。可以理解为,是让出了线程,跳出了 async 函数体。

举个例子:

async function func1() {
    return 1
}

console.log(func1())

在这里插入图片描述
很显然,func1的运行结果其实就是一个Promise对象。因此我们也可以使用then来处理后续逻辑。

func1().then(res => {
    console.log(res);  // 30
})

await的含义为等待,也就是 async 函数需要等待await后的函数执行完成并且有了返回结果(Promise对象)之后,才能继续执行下面的代码。await通过返回一个Promise对象来实现同步的效果。

更多可见setTimeout、Promise、Async/Await

针对最后两步的结果,为什么是script end->async1 end?async1到async2之后没有返回到async1?可以这么理解吗:async1中对于async1 end实际上相当于async2中await返回的Promise的then回调吗?所以被归类到异步任务队列中去了?而外部的script end是一个同步的任务,所以先打印出来了

@LoKiLyn
Copy link

LoKiLyn commented Mar 19, 2021

@sisterAn 你好,关于你的 3.async/await 我觉得不是很完备。

async function async1(){
   console.log('async1 start');
   // 我理解是这里的 async2 函数是返回的 Premise 对象
   // 但是 Premise 其本身就是一个宏任务 不用 await 也应该是同步执行的
    await async2();
    console.log('async1 end')
}
async function async2(){
    console.log('async2')
}

console.log('script start');
async1();
console.log('script end')

// 输出顺序:script start->async1 start->async2->script end->async1 end

而且请看一下下面例子是否能够表明清楚
或者有什么不对的地方

// 被添加入下一次宏任务队列
setTimeout(_ => console.log(6));

async function test() {
  console.log(1);
  // 我的理解是
  // 遇到 await 先执行其同步内容
  // 让出执行线程(跳出当前任务)同时推入微任务队列  继续往下执行
  // 执行完之后 回来继续执行
  await new Promise((resolve, reject) => {
    console.log(2);
    resolve();
  }).then(_ => console.log(4));
  // 这里 5 没有进入微任务队列 只不是相当于被挂起了
  console.log(5);
}

test();
test();

console.log(3);
// 如果执行一次 test 函数  结果 => 123456
// 如果执行两次 test 函数  结果 => 1212344556

请教一下 执行两次test函数为什么不是 1212345456 呢.

@shifengdiy
Copy link

shifengdiy commented Jul 25, 2021

js异步执行流程

Event Loop

首先js执行由三部分组成,用于执行主线程的栈,叫做execution context stack,用于存放在对象数据的heap,一般使用内存,用于存放异步事件队列的event queue

  1. 先说一下主线程的栈的执行流程,每个执行函数会形成一个帧,若干个帧组成了调用栈,然后依据先进后出的执行方式,从最后一个帧执行到第一个帧,前面的帧拥有后面帧的上下文,这个就是层级上下文,每个函数执行完毕之后,就弹出这一帧,销毁当前上下文,当所有的帧执行完毕之后,调用栈就被清空了,主线程此时执行完毕,也就是所谓的主线程闲置状态
  2. 执行过程产的数据存放在heap中,相应的上下文销毁后,内部的数据对象就会被回收
  3. 同时,在主线程执行过程中会产生一些异步事件,这些事件会被推入一个叫做callback queue的队列中,先进先出,异步事件的执行取决于event loop,event loop会轮询event queue,每当主调用栈处于闲置状态,事件队列中一个事件就会被推入主调用栈,继续执行步骤1、2、3;以此循环实现event loop
  4. 大概说一下异步事件的来源和类型,
  • settimeout和setintel产生的事件回调,由定时器线程来管理定时任务,当一个定时任务被激活,一个事件就会被推入事件队列
  • ajax网络请求的事件回调,由专门的网络请求线程管理
  • IO操作,包括鼠标点击,键盘输入和手势操作,
  • DOM更改,如requestAnimationFrame事件,Image的onload,document的onload
    补充一下几乎 所有的异步事件都有专门的线程来管理,event loop只负责将队列中的事件推入调用栈,至于在什么时机推入,都是由专门的线程负责,定时器模块借助CPU时钟来定时推入事件,DOM的更改事件由渲染进程负责

宏任务与微任务

  • 微任务的调度会有一个单独的 queueMicroTask,微任务事件队列依赖于宏任务的执行,当主调用栈清空,event loop检查微微任务队列,然后全部执行完毕,才能执行下一个宏任务,在此期间如果产生新的微任务,就会推入一个新的微任务队列,大概可以理解为每个宏任务有一个属于自己的微任务队列,每当本身执行完毕,还要执行 在此期间产生的 附属的微任务队列
  • 最常见的微任务之一就是 promise 的then、finally、catch方法,当这些方法需要运行,就会产生一个微任务

@Ha0ran2001
Copy link

宏观微观?我记得promise的执行是在setimeout之前(好像),async/await是基于promise的

因为其实setTimeout不是JavaScript的api,所以是在宏任务里,JavaScript的代码都是在微任务里执行的,所以执行之后才执行setTimeout的代码

@Qyokizzzz
Copy link

传给setTimeout的回调是异步的,会放在宏任务队列里;
调用Promise的构造函数是同步的,传给Promise.then、Promise.catch、Promise.finally的回调是异步的,会放在微任务队列里;
async函数体内的第一个await表达式是宏任务,下面的都算微任务。

@Yangfan2016
Copy link

Yangfan2016 commented Aug 23, 2022

都是异步任务

setTimeout 属于宏任务
promise async/await 属于微任务

await 表达式会暂停整个 async 函数的执行进程并出让其控制权,只有当其等待的基于 promise 的异步操作被兑现或被拒绝之后才会恢复进程。promise 的解决值会被当作该 await 表达式的返回值

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/async_function

async function async1(){
   console.log('2');
    await async2();
    console.log('5')
}
async function async2(){
    console.log('3')
}

console.log('1');
async1();
console.log('4')
 // 1 2 3 4 5

@jumplee
Copy link

jumplee commented Jan 7, 2023

setTimout

这是运行环境代码,和js语言无关。所以chrome,Firefox,nodejs等运行环境对其实现可能不一样,但最后实现结果都一样,延迟执行一段代码。nodejs实现是通过timer,当然可以看c++源码,本质上很简单,就是死循环获取断点,实现event loop。

promise

promise就是callback函数的封装,通过js就可以自己实现一个。现在是通过c++等原生实现了一遍。相比回调函数,promise的逻辑更清晰,但最重要的是代码编写格式统一,可以实现race,all,等异步管理,rxjs就是非常好的工具。因为promise的统一格式,通过yield关键词,就可以封装出await和async,实现异步代码简化。

await和async

首先es6增加了yield关键词和generator函数,实现了编程语言上的协程。具体实现可以看babel的转换代码,v8只是先进行语义分析,然后生成相应的generator函数,逻辑和babel一样。因为其中涉及到了具体代码,所以无法写出公共函数,总之是一种语言逻辑转换,通过死循环和switch返回结果来实现代码的挂起和执行。yield加promise的语法糖就是await和async。总之,异步代码最佳结果就是await和async,一般不需要yield代码,特别复杂使用rxjs。

@Bibooo25730
Copy link

自己回答一波,若有错误请各位指出,互相学习!谢谢 ~ 鉴于各位上面大佬回答的,其实这题主要还是考浏览器的EVENT LOOP setTimeout属于宏任务,Promise属于微任务.而async和await其实是geneorator的语法糖,实质上最后返回的也是promise,所以我将其归为微任务(有错的,请指出,谢谢)。 而浏览器的执行顺序是在一开始会通篇扫描整个脚本,生成主执行栈,用于执行同步任务.而异步任务会加入至浏览器的任务队列中.当执行栈为空,就会去Task队列中(任务队列)取出需要执行的代码放入执行栈中去执行。而Task队列中,我们又再之前提及到分:微任务和宏任务 微任务的优先级大于宏任务,所以在执行栈为空的时候,首先会去执行Micortask(微任务)队列,执行完毕后再去取Macrotask(任务)队列去执行栈中执行,一次执行一个,再去检查Micortask(微任务),若存在则执行Microtask,若没有则取下一个Macrotask任务继续执行,直至为空。 以上,是我对EventLoop的回答,但是我还是想在此基础上回答下关于这三个的异步不同之处。

设置超时

setTimeout的异步使用方法算是比较古老的回调函数方式,就是我们之前写Jquery的时候,ajax的最常见的使用方式,这种的好处在于用很简单的方式实现了异步的方式,从而解决了异步直肠子的问题(耗时任务,一直处于等待)。缺点:回调地狱,这是写了多年Jq的一直很恶心的地方,代码嵌套太多,牵一发而动全身。

承诺

优点:解决了回调函数的问题,可以使用链式结构,代码逻辑清晰。 缺点:无法取消,有时逻辑复杂then太多,then中嵌套then,错误捕获不到正确的位置,只能通过自己的catch或者写reject(erro)的回调来捕获。

异步

至此,我们再次引出我们的Async/Await语法糖 优点:以同步代码的方式去写异步,代码逻辑清晰,符合我们平时写的逻辑。缺点:因为await将异步代码改造成了同步代码,如果多个异步代码没有依赖性却使用了await会导致性能上的降低。(这个是可以避免的)

??而浏览器的执行顺序是在一开始会通篇扫描整个脚本,生成主执行栈,用于执行同步任务.而异步任务会加入至浏览器的任务队列中.当执行栈为空,就会去Task队列中(任务队列)取出需要执行的代码放入执行栈中去执行。 :生成主执行栈,用于执行同步任务. 执行栈都为空了还执行个什么呢?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests