>  기사  >  웹 프론트엔드  >  JavaScript의 비동기 프로그래밍 방법은 무엇입니까? JavaScript 비동기 프로그래밍 방법 소개

JavaScript의 비동기 프로그래밍 방법은 무엇입니까? JavaScript 비동기 프로그래밍 방법 소개

不言
不言앞으로
2019-01-14 09:35:092520검색

이 기사에서는 JavaScript의 비동기 프로그래밍 방법에 대해 설명합니다. JavaScript 비동기 프로그래밍 방법의 도입에는 특정 참고 가치가 있습니다. 도움이 필요한 친구가 도움이 되기를 바랍니다.

우리는 Javascript 언어의 실행 환경이 "단일 스레드"라는 것을 알고 있습니다. 이는 한 번에 하나의 작업만 완료할 수 있음을 의미합니다. 여러 작업이 있는 경우 대기열에 넣어야 하며 이전 작업이 완료된 후 다음 작업이 실행됩니다.

이 모드는 구현하기가 비교적 간단하고 실행 환경도 비교적 간단하지만, 하나의 작업이 오랜 시간이 걸리는 만큼 후속 작업을 대기열에 넣어야 하므로 전체 프로그램의 실행이 지연됩니다. . 일반적인 브라우저 무응답(일시 중단)은 장시간 실행되는 특정 Javascript 코드 조각(예: 무한 루프)으로 인해 발생하는 경우가 많습니다. 이로 인해 전체 페이지가 이 위치에 멈춰 다른 작업을 수행할 수 없게 됩니다.

이 문제를 해결하기 위해 Javascript 언어에서는 작업 실행 모드를 동기식과 비동기식의 두 가지 유형으로 나눕니다. 이 기사에서는 주로 비동기 프로그래밍의 여러 방법을 소개하며 비교를 통해 최고의 비동기 프로그래밍 솔루션을 얻습니다!

1. 동기화 및 비동기성

우리는 일반적으로 작업이 두 부분으로 나누어지고 첫 번째 부분이 실행된다는 것을 이해할 수 있습니다. 먼저, 다른 작업을 수행할 때 다시 돌아가서 두 번째 단락을 수행할 준비가 될 때까지 기다리세요. 비동기 작업 뒤에 순위가 매겨진 코드는 비동기 작업이 끝날 때까지 기다리지 않고 즉시 실행됩니다. 즉, 비동기 작업에는 "차단" 효과가 없습니다. 예를 들어, 처리를 위해 파일을 읽는 작업이 있습니다. 비동기 실행 프로세스는 다음과 같습니다. 연속 실행을 비동기라고 합니다. 이에 따라 연속 실행을 동기화라고 합니다. 브라우저 측에서는 브라우저가 응답하지 않는 것을 방지하기 위해 장기 실행 작업을 비동기식으로 수행해야 합니다. 가장 좋은 예는 Ajax 작업입니다. 서버 측에서는 실행 환경이 단일 스레드이기 때문에 "비동기 모드"가 유일한 모드이기도 하며, 모든 http 요청이 동기적으로 실행되도록 허용하면 서버 성능이 급격히 떨어지고 매우 빠르게 응답하지 않게 됩니다. 다음으로 비동기 프로그래밍의 6가지 방법을 소개하겠습니다.

2. 콜백 함수(Callback) JavaScript의 비동기 프로그래밍 방법은 무엇입니까? JavaScript 비동기 프로그래밍 방법 소개

콜백 함수는 비동기 작업의 가장 기본적인 방법입니다. 다음 코드는 콜백 함수의 예입니다.

ajax(url, () => {
    // 处理逻辑
})
하지만 콜백 함수에는 쓰기 쉽다는 치명적인 약점이 있습니다.

Callback hell. 여러 요청에 종속성이 있다고 가정하면 다음 코드를 작성할 수 있습니다. JavaScript의 비동기 프로그래밍 방법은 무엇입니까? JavaScript 비동기 프로그래밍 방법 소개

ajax(url, () => {
    // 处理逻辑
    ajax(url1, () => {
        // 处理逻辑
        ajax(url2, () => {
            // 处理逻辑
        })
    })
})
콜백 함수의 장점은 간단하고 이해하고 구현하기 쉽다는 것이지만 단점은 도움이 되지 않는다는 것입니다. 각 부분 사이의 높은 결합으로 인해 프로그램 구조가 혼란스럽고 프로세스를 추적하기가 어렵고(특히 여러 콜백 함수가 중첩된 경우) 각 작업에 하나의 콜백 함수만 지정할 수 있습니다. 또한 try catch를 사용하여 오류를 잡을 수 없으며 직접 반환할 수도 없습니다.

3. 이벤트 모니터링

이렇게 하면

비동기 작업의 실행은 코드 순서에 영향을 받지 않습니다. , 그러나 특정 이벤트가 발생하는지 여부

.

다음은 f1과 f2의 두 가지 함수입니다. 프로그래밍 의도는 f2가 실행되기 전에 f1이 완료될 때까지 기다려야 한다는 것입니다. 먼저 이벤트를 f1에 바인딩합니다(여기서 사용된 jQuery 작성 방법)

f1.on('done', f2);
위 코드 줄은 f1에서 done 이벤트가 발생하면 f2가 실행된다는 의미입니다. 그런 다음 f1을 다시 작성합니다.

function f1() {
  setTimeout(function () {
    // ...
    f1.trigger('done');
  }, 1000);
}

위 코드에서 f1.trigger('done')은 실행이 완료된 후 done 이벤트가 즉시 트리거되어 f2 실행이 시작됨을 의미합니다.

이 방법의 장점은 비교적 이해하기 쉽고, 여러 이벤트를 바인딩할 수 있고, 각 이벤트가 여러 콜백 함수를 지정할 수 있으며, "분리"될 수 있어 모듈화에 도움이 된다는 것입니다. 단점은 전체 프로그램이 이벤트 중심으로 이루어져야 하고 실행 프로세스가 매우 불분명해진다는 것입니다. 코드를 읽어보면 주요 흐름을 파악하기 어렵습니다.

4. 게시 및 구독

"신호 센터"가 있다고 가정하고 작업이 완료되면 게시됩니다. 신호 센터에" "(게시) 신호를 게시하면 다른 작업은 신호 센터의 신호를 "구독"하여 언제 실행을 시작할 수 있는지 알 수 있습니다. 이를 "게시-구독 패턴"(게시-구독 패턴)이라고 하며 "관찰자 패턴"(관찰자 패턴)이라고도 합니다.

먼저, f2는 시그널 센터 jQuery에서 완료된 시그널을 구독합니다.

jQuery.subscribe('done', f2);

그러면 f1은 다음과 같이 다시 작성됩니다.

function f1() {
  setTimeout(function () {
    // ...
    jQuery.publish('done');
  }, 1000);
}

위 코드에서 jQuery.publish('done')은 f1의 실행이 완료된 후 신호 센터에 대한 jQuery 완료 신호를 해제하여 f2의 실행을 트리거합니다.

f2 실행이 완료되면 구독을 취소하실 수 있습니다

jQuery.unsubscribe('done', f2);

这种方法的性质与“事件监听”类似,但是明显优于后者。因为可以通过查看“消息中心”,了解存在多少信号、每个信号有多少订阅者,从而监控程序的运行。

五、Promise/A+

Promise本意是承诺,在程序中的意思就是承诺我过一段时间后会给你一个结果。 什么时候会用到过一段时间?答案是异步操作,异步是指可能比较长时间才有结果的才做,例如网络请求、读取本地文件等

1.Promise的三种状态

  • Pending----Promise对象实例创建时候的初始状态

  • Fulfilled----可以理解为成功的状态

  • Rejected----可以理解为失败的状态

JavaScript의 비동기 프로그래밍 방법은 무엇입니까? JavaScript 비동기 프로그래밍 방법 소개

这个承诺一旦从等待状态变成为其他状态就永远不能更改状态了,比如说一旦状态变为 resolved 后,就不能再次改变为Fulfilled

let p = new Promise((resolve, reject) => {
  reject('reject')
  resolve('success')//无效代码不会执行
})
p.then(
  value => {
    console.log(value)
  },
  reason => {
    console.log(reason)//reject
  }
)

当我们在构造 Promise 的时候,构造函数内部的代码是立即执行的

new Promise((resolve, reject) => {
  console.log('new Promise')
  resolve('success')
})
console.log('end')
// new Promise => end

2.promise的链式调用

  • 每次调用返回的都是一个新的Promise实例(这就是then可用链式调用的原因)

  • 如果then中返回的是一个结果的话会把这个结果传递下一次then中的成功回调

  • 如果then中出现异常,会走下一个then的失败回调

  • 在 then中使用了return,那么 return 的值会被Promise.resolve() 包装(见例1,2)

  • then中可以不传递参数,如果不传递会透到下一个then中(见例3)

  • catch 会捕获到没有捕获的异常

接下来我们看几个例子:

  // 例1
  Promise.resolve(1)
  .then(res => {
    console.log(res)
    return 2 //包装成 Promise.resolve(2)
  })
  .catch(err => 3)
  .then(res => console.log(res))
// 例2
Promise.resolve(1)
  .then(x => x + 1)
  .then(x => {
    throw new Error('My Error')
  })
  .catch(() => 1)
  .then(x => x + 1)
  .then(x => console.log(x)) //2
  .catch(console.error)
// 例3
let fs = require('fs')
function read(url) {
  return new Promise((resolve, reject) => {
    fs.readFile(url, 'utf8', (err, data) => {
      if (err) reject(err)
      resolve(data)
    })
  })
}
read('./name.txt')
  .then(function(data) {
    throw new Error() //then中出现异常,会走下一个then的失败回调
  }) //由于下一个then没有失败回调,就会继续往下找,如果都没有,就会被catch捕获到
  .then(function(data) {
    console.log('data')
  })
  .then()
  .then(null, function(err) {
    console.log('then', err)// then error
  })
  .catch(function(err) {
    console.log('error')
  })

Promise不仅能够捕获错误,而且也很好地解决了回调地狱的问题,可以把之前的回调地狱例子改写为如下代码:

ajax(url)
  .then(res => {
      console.log(res)
      return ajax(url1)
  }).then(res => {
      console.log(res)
      return ajax(url2)
  }).then(res => console.log(res))

它也是存在一些缺点的,比如无法取消 Promise,错误需要通过回调函数捕获。

六、生成器Generators/ yield

Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同,Generator 最大的特点就是可以控制函数的执行。

  • 语法上,首先可以把它理解成,Generator 函数是一个状态机,封装了多个内部状态。

  • Generator 函数除了状态机,还是一个遍历器对象生成函数

  • 可暂停函数, yield可暂停,next方法可启动,每次返回的是yield后的表达式结果

  • yield表达式本身没有返回值,或者说总是返回undefined。next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值

我们先来看个例子:

function *foo(x) {
  let y = 2 * (yield (x + 1))
  let z = yield (y / 3)
  return (x + y + z)
}
let it = foo(5)
console.log(it.next())   // => {value: 6, done: false}
console.log(it.next(12)) // => {value: 8, done: false}
console.log(it.next(13)) // => {value: 42, done: true}

可能结果跟你想象不一致,接下来我们逐行代码分析:

  • 首先 Generator 函数调用和普通函数不同,它会返回一个迭代器

  • 当执行第一次 next 时,传参会被忽略,并且函数暂停在 yield (x + 1) 处,所以返回 5 + 1 = 6

  • 当执行第二次 next 时,传入的参数12就会被当作上一个yield表达式的返回值,如果你不传参,yield 永远返回 undefined。此时 let y = 2 12,所以第二个 yield 等于 2 12 / 3 = 8

  • 当执行第三次 next 时,传入的参数13就会被当作上一个yield表达式的返回值,所以 z = 13, x = 5, y = 24,相加等于 42

我们再来看个例子:有三个本地文件,分别1.txt,2.txt和3.txt,内容都只有一句话,下一个请求依赖上一个请求的结果,想通过Generator函数依次调用三个文件

//1.txt文件
2.txt
//2.txt文件
3.txt
//3.txt文件
结束
let fs = require('fs')
function read(file) {
  return new Promise(function(resolve, reject) {
    fs.readFile(file, 'utf8', function(err, data) {
      if (err) reject(err)
      resolve(data)
    })
  })
}
function* r() {
  let r1 = yield read('./1.txt')
  let r2 = yield read(r1)
  let r3 = yield read(r2)
  console.log(r1)
  console.log(r2)
  console.log(r3)
}
let it = r()
let { value, done } = it.next()
value.then(function(data) { // value是个promise
  console.log(data) //data=>2.txt
  let { value, done } = it.next(data)
  value.then(function(data) {
    console.log(data) //data=>3.txt
    let { value, done } = it.next(data)
    value.then(function(data) {
      console.log(data) //data=>结束
    })
  })
})
// 2.txt=>3.txt=>结束

从上例中我们看出手动迭代Generator 函数很麻烦,实现逻辑有点绕,而实际开发一般会配合 co 库去使用。co是一个为Node.js和浏览器打造的基于生成器的流程控制工具,借助于Promise,你可以使用更加优雅的方式编写非阻塞代码

安装co库只需:npm install co

上面例子只需两句话就可以轻松实现

function* r() {
  let r1 = yield read('./1.txt')
  let r2 = yield read(r1)
  let r3 = yield read(r2)
  console.log(r1)
  console.log(r2)
  console.log(r3)
}
let co = require('co')
co(r()).then(function(data) {
  console.log(data)
})
// 2.txt=>3.txt=>结束=>undefined

我们可以通过 Generator 函数解决回调地狱的问题,可以把之前的回调地狱例子改写为如下代码:

function *fetch() {
    yield ajax(url, () => {})
    yield ajax(url1, () => {})
    yield ajax(url2, () => {})
}
let it = fetch()
let result1 = it.next()
let result2 = it.next()
let result3 = it.next()

七、async/await

1.Async/Await简介

使用async/await,你可以轻松地达成之前使用生成器和co函数所做到的工作,它有如下特点:

  • async/await是基于Promise实现的,它不能用于普通的回调函数。

  • async/await与Promise一样,是非阻塞的。

  • async/await使得异步代码看起来像同步代码,这正是它的魔力所在。

一个函数如果加上 async ,那么该函数就会返回一个 Promise

async function async1() {
  return "1"
}
console.log(async1()) // -> Promise {<resolved>: "1"}</resolved>

Generator函数依次调用三个文件那个例子用async/await写法,只需几句话便可实现

let fs = require('fs')
function read(file) {
  return new Promise(function(resolve, reject) {
    fs.readFile(file, 'utf8', function(err, data) {
      if (err) reject(err)
      resolve(data)
    })
  })
}
async function readResult(params) {
  try {
    let p1 = await read(params, 'utf8')//await后面跟的是一个Promise实例
    let p2 = await read(p1, 'utf8')
    let p3 = await read(p2, 'utf8')
    console.log('p1', p1)
    console.log('p2', p2)
    console.log('p3', p3)
    return p3
  } catch (error) {
    console.log(error)
  }
}
readResult('1.txt').then( // async函数返回的也是个promise
  data => {
    console.log(data)
  },
  err => console.log(err)
)
// p1 2.txt
// p2 3.txt
// p3 结束
// 结束

2.Async/Await并发请求

如果请求两个文件,毫无关系,可以通过并发请求

let fs = require('fs')
function read(file) {
  return new Promise(function(resolve, reject) {
    fs.readFile(file, 'utf8', function(err, data) {
      if (err) reject(err)
      resolve(data)
    })
  })
}
function readAll() {
  read1()
  read2()//这个函数同步执行
}
async function read1() {
  let r = await read('1.txt','utf8')
  console.log(r)
}
async function read2() {
  let r = await read('2.txt','utf8')
  console.log(r)
}
readAll() // 2.txt 3.txt

八、总结

1.JS 异步编程进化史:callback -> promise -> generator -> async + await

2.async/await 函数的实现,就是将 Generator 函数和自动执行器,包装在一个函数里。

3.async/await可以说是异步终极解决方案了。

(1) async/await函数相对于Promise,优势体现在

  • 处理 then 的调用链,能够更清晰准确的写出代码

  • 并且也能优雅地解决回调地狱问题。

当然async/await函数也存在一些缺点,因为 await 将异步代码改造成了同步代码,如果多个异步代码没有依赖性却使用了 await 会导致性能上的降低,代码没有依赖性的话,完全可以使用 Promise.all 的方式。

(2) async/await函数对 Generator 函数的改进,体现在以下三点

  • 内置执行器。

Generator 函数的执行必须靠执行器,所以才有了 co 函数库,而 async 函数自带执行器。也就是说,async 函数的执行,与普通函数一模一样,只要一行

  • 更广的适用性。 co 函数库约定,yield 命令后面只能是 Thunk 函数或 Promise 对象,而 async 函数的 await 命令后面,可以跟 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)

  • 更好的语义。 async 和 await,比起星号和 yield,语义更清楚了。async 表示函数里有异步操作,await 表示紧跟在后面的表达式需要等待结果。

위 내용은 JavaScript의 비동기 프로그래밍 방법은 무엇입니까? JavaScript 비동기 프로그래밍 방법 소개의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

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