>  기사  >  웹 프론트엔드  >  JavaScript에서 비동기가 처리되는 방식 이해

JavaScript에서 비동기가 처리되는 방식 이해

青灯夜游
青灯夜游앞으로
2020-11-20 17:29:059354검색

JavaScript에서 비동기가 처리되는 방식 이해

웹사이트 개발에서 비동기 이벤트는 프로젝트가 반드시 처리해야 하는 링크입니다. 또한 프론트엔드 프레임워크의 등장으로 인해 프레임워크를 통해 구현된 SPA는 웹사이트를 빠르게 구축하기 위한 표준이 되었습니다. ; 이 기사에서는 JavaScript의 비동기 처리에 대해 설명합니다.

동기화하시겠습니까? 비동기?

우선 동기화와 비동기가 각각 무엇을 가리키는지 이해해야 합니다.

초심자에게는 이 두 용어가 항상 혼란스럽습니다. 결국 중국어의 문자 그대로의 의미는 정보 과학의 관점에서 보면 동기화라는 것은 여러 가지를 동시에 처리하는 것을 의미합니다. 평행한.

예를 들어, 업무를 처리하기 위해 은행에 갈 때 창구 앞에 줄을 서는 것이 동기 실행이고, 번호를 받고 다른 일을 먼저 하는 것이 JavaScript의 비동기 이벤트인 Event Loop의 특성을 통한 비동기 실행입니다. 케이크 조각이라고 할 수 있습니다

그럼 JavaScript에서 비동기 이벤트를 처리하는 방법은 무엇일까요?

콜백 함수

우리에게 가장 친숙한 함수는 콜백 함수입니다. 예를 들어, 웹 페이지가 사용자와 상호 작용할 때 등록된 이벤트 리스너는 콜백 함수를 수신해야 하거나 setTimeoutxhr와 같은 다른 웹 API 함수도 콜백을 수신해야 합니다. 함수. 사용자가 요구하는 시간에 콜백 함수를 전달하여 트리거할 수 있습니다. 먼저 setTimeout의 예를 살펴보겠습니다. setTimeoutxhr,也都能通过传递回调函数在用户要求的时机去触发。先看一个 setTimeout 的例子:

// callback
function withCallback() {
  console.log('start')
  setTimeout(() => {
    console.log('callback func')
  }, 1000)
  console.log('done')
}withCallback()
// start
// done
// callback func

setTimeout 被执行后,当过了指定的时间间隔之后,回调函数会被放到队列的末端,再等待事件循环处理到它。

注意:也就时因为这种机制,开发者设定给 setTimeout 的时间间隔,并不会精准的等于从执行到触发所经过的时间,使用时要特别注意!

回调函数虽然在开发中十分常见,但也有许多难以避免的问题。例如由于函数需要被传递给其他函数,开发者难以掌控其他函数内的处理逻辑;又因为回调函数仅能配合 try … catch 捕捉错误,当异步错误发生时难以控制;另外还有最著名的“回调地狱”。

Promise

幸好在 ES6 之后出现了 Promise,拯救了身陷在地狱的开发者们。其基本用法也很简单:

function withPromise() {
  return new Promise(resolve => {
    console.log('promise func')
    resolve()
  })
}
withPromise()
  .then(() => console.log('then 1'))
  .then(() => console.log('then 2'))
// promise func
// then 1
// then 2

之前讨论 Event Loop 时没有提到的是,在HTML 5 的Web API 标准 中,Event Loop 新增了微任务队列(micro task queue),而 Promise 正是通过微任务队列来驱动它的;微任务队列的触发时机是在栈被清空时,JavaScript 引擎会先确认微任务队列有没有东西,有的话就优先执行,直到清空后才从队列拿出新任务到栈上。

如上面的例子,当函数回传一个 Promise 时,JavaScript 引擎便会把后传入的函数放到微任务队列中,反复循环,输出了上列的结果。后续的  .then 语法会回传一个新的 Promise,参数函数则接收前一个 Promise.resolve 的结果,凭借这样函数参数传递,让开发者可以管道式的按顺序处理异步事件。

如果在例子中加上 setTimeout 就更能清楚理解微任务与一般任务的差别:

function withPromise() {
  return new Promise(resolve => {
    console.log('promise func')
    resolve()
  })
}
withPromise()
  .then(() => console.log('then 1'))
  .then(() => setTimeout(() => console.log('setTimeout'), 0))
  .then(() => console.log('then 2'))
// promise func
// then 1
// then 2 -> 微任务优先执行
// setTimeout

另外,前面所说的回调函数很难处理的异步错误,也可以通过 .catch 语法来捕获。

function withPromise() {
  return new Promise(resolve => {
    console.log('promise func')
    resolve()
  })
}
withPromise()
  .then(() => console.log('then 1'))
  .then(() => { throw new Error('error') })
  .then(() => console.log('then 2'))
  .catch((err) => console.log('catch:', err))
// promise func
// then 1
// catch: error
//   ...error call stack

async await

从 ES6 Promise 问世之后,异步代码从回呼地狱逐渐变成了优雅的函数式管道处理,但对于不熟悉度的开发者来说,只不过是从回调地狱变成了 Promise 地狱而已。

在 ES8 中规范了新的 async/await,虽然只是 Promise 和 Generator Function组合在一起的语法糖,但通过 async/await 便可以将异步事件用同步语法来处理,就好像是老树开新花一样,写起来的风格与 Promise 完全不同:

function wait(time, fn) {
  return new Promise(resolve => {
    setTimeout(() => {
      console.log('wait:', time)
      resolve(fn ? fn() : time)
    }, time)
  })
}
await wait(500, () => console.log('bar'))
console.log('foo')
// wait: 500
// bar
// foo

通过把 setTimeout 包装成 Promise,再用 await 关键字调用,可以看到结果会是同步执行的先出现 bar,再出现 foo,也就是开头提到的将异步事件写成同步处理。

再看一个例子:

async function withAsyncAwait() {
  for(let i = 0; i < 5; i++) {
    await wait(i*500, () => console.log(i))
  }
}await withAsyncAwait()
// wait: 0
// 0
// wait: 500
// 1
// wait: 1000
// 2
// wait: 1500
// 3
// wait: 2000
// 4

代码中实现了withAsyncAwait 函数,用 for 循环及 await 关键字反复执行 wait 函数;此处执行时,循环每次会按顺序等待不同的秒数再执行下一次循环。

在使用 async/await 时,由于 awaitrrreee

setTimeout이 실행된 후 지정된 시간 간격이 지나면 콜백 함수가 setTimeout의 끝에 배치됩니다. 대기열에 넣은 다음 이벤트 루프가 이를 처리할 때까지 기다립니다. 🎜
참고: 이 메커니즘으로 인해 개발자가 setTimeout에 설정한 시간 간격은 실행에서 트리거까지 경과된 시간과 정확히 동일하지 않습니다. !
🎜콜백 함수는 개발에서 매우 일반적이지만, 피하기 어려운 문제도 많이 있습니다. 예를 들어, 함수는 다른 함수로 전달되어야 하기 때문에 개발자가 다른 함수의 처리 로직을 제어하기 어렵고 콜백 함수는 잡기 위해 try...catch에만 협력할 수 있기 때문입니다. 오류, 비동기 오류가 발생할 때 제어하기가 어렵습니다. 또한 가장 유명한 "콜백 지옥"도 있습니다. 🎜🎜🎜Promise🎜🎜🎜다행히 ES6 이후 등장한 Promise는 지옥에 갇힌 개발자들을 구해냈습니다. 기본 사용법도 매우 간단합니다. 🎜rrreee🎜이전에 Event Loop를 논의할 때 언급되지 않은 것은 HTML 5 Web API 표준에서 Event Loop가 마이크로 작업 대기열을 추가했고 Promise는 마이크로 작업 대기열을 사용한다는 것입니다. 태스크 큐는 스택이 지워질 때 트리거됩니다. JavaScript 엔진은 먼저 마이크로 태스크 큐에 아무것도 없는지 확인합니다. 스택에서 지워질 때까지 대기열에서 새 작업을 수행합니다. 🎜🎜위의 예에서와 같이 함수가 Promise를 반환하면 JavaScript 엔진은 나중에 전달된 함수를 마이크로태스크 대기열에 넣고 반복적으로 루프를 실행하며 위에 나열된 결과를 출력합니다. 후속 .then 구문은 새 Promise를 반환하고 매개변수 함수는 이전 Promise.resolve의 결과를 수신합니다. 이 함수 매개변수 전달을 통해 개발자는 핸들을 파이프라인할 수 있습니다. 비동기 이벤트를 순차적으로 처리합니다. 🎜🎜예제에 setTimeout을 추가하면 마이크로태스크와 일반태스크의 차이를 더욱 명확하게 이해할 수 있습니다. 🎜rrreee🎜그리고 앞서 언급한 콜백 함수로는 처리하기 어려운 비동기 오류도 .catch 구문을 전달하여 캡처할 수도 있습니다. 🎜rrreee🎜🎜async wait🎜🎜🎜ES6 Promise의 등장 이후 비동기 코드는 점차 콜백 지옥에서 우아한 기능적 파이프라인 처리로 바뀌었지만, 익숙하지 않은 개발자에게는 그저 콜백 지옥의 변화일 뿐입니다. 그냥 지옥을 약속해. 🎜🎜새로운 async/await는 ES8에서 표준화되었습니다. 이는 단지 Promise와 Generator 함수를 결합하기 위한 구문 설탕일 뿐이지만 async를 통해 전달됩니다. > /await는 오래된 나무가 새 꽃을 피우는 것처럼 동기 구문을 사용하여 비동기 이벤트를 처리할 수 있습니다. 작성 스타일은 Promise와 완전히 다릅니다. 🎜rrreee🎜setTimeout code>를 사용합니다. Promise로 패키징된 후 <code>await 키워드를 사용하여 호출하면 bar가 먼저 나타나고 foo가 나타나는 것을 볼 수 있습니다. 가 나타납니다. 즉, 처음에 언급한 동기 처리에 비동기 이벤트를 작성하는 것입니다. 🎜🎜또 다른 예를 보세요: 🎜rrreee🎜withAsyncAwait 함수가 코드에 구현되어 있고 for 루프와 await 키워드가 사용됩니다. wait 함수를 반복적으로 실행하려면 여기에서 실행하면 루프는 다음 루프를 실행하기 전에 순서대로 다른 시간(초) 동안 대기합니다. 🎜🎜async/await를 사용할 때 await 키워드는 async 함수에서만 실행될 수 있으므로 꼭 기억하세요. 같은 시간. 🎜

또한 루프를 사용하여 비동기 이벤트를 처리하는 경우 ES6 이후에 제공되는 많은 Array 메서드는 지원되지 않으며async/await 语法,如果这里用 forEach 取代 for 결과는 동기 실행이 되어 0.5초마다 숫자가 인쇄된다는 점에 유의해야 합니다.

요약

이 기사는 간단합니다. 이 기사에서는 JavaScript가 비동기 처리를 처리하는 세 가지 방법을 소개하고, 앞서 언급한 이벤트 루프를 반영하는 몇 가지 간단한 예를 통해 코드 실행 순서를 설명하고 여기에 마이크로태스크 대기열의 개념을 추가합니다. 동기식 및 비동기식 애플리케이션을 이해하는 데 도움이 되기를 바랍니다.

더 많은 프로그래밍 관련 지식을 보려면 프로그래밍 소개를 방문하세요! !

위 내용은 JavaScript에서 비동기가 처리되는 방식 이해의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 segmentfault.com에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제