>  기사  >  웹 프론트엔드  >  JS 비동기 기능을 최적화하는 방법

JS 비동기 기능을 최적화하는 방법

php中世界最好的语言
php中世界最好的语言원래의
2018-05-30 10:16:541511검색

이번에는 js async 기능을 최적화하는 방법과 js async 기능을 최적화할 때 주의 사항이 무엇인지 보여드리겠습니다. 다음은 실제 사례입니다.

먼저 Promise를 이해해야 합니다

Promise는 async/await를 사용하는 기본이므로 Promise가 무엇을 하는지 먼저 이해해야 합니다

Promise는 콜백 지옥을 해결하는 데 도움이 되는 좋은 기능이며, 비동기화를 할 수 있습니다. 프로세스가 더욱 명확해집니다.

Error-first-callback을 Promise로 변환하는 간단한 예:

const fs = require('fs')
function readFile (fileName) {
 return new Promise((resolve, reject) => {
  fs.readFile(fileName, (err, data) => {
   if (err) reject(err)
   resolve(data)
  })
 })
}
readFile('test.log').then(data => {
 console.log('get data')
}, err => {
 console.error(err)
})

함수를 호출하여 Promise 인스턴스를 반환하고 인스턴스화 프로세스 중에 파일을 읽습니다. 파일 읽기를 위한 콜백이 트리거되면 then을 사용하여 다음을 수행합니다. Promise 상태, 해결 또는 거부 상태의 변경 사항을 모니터링합니다. 첫 번째 콜백은 해결 처리를 위한 것이고 두 번째 콜백은 거부 처리를 위한 것입니다.

async와 Promise의 관계

async 함수는 Promise 인스턴스를 반환하는 단축 함수와 동일합니다. 효과는 다음과 같습니다.

function getNumber () {
 return new Promise((resolve, reject) => {
  resolve(1)
 })
}
// =>
async function getNumber () {
 return 1
}

둘은 사용 시 완전히 동일하며 이후에 사용할 수 있습니다. getNumber 함수를 호출한 다음 반환 값을 모니터링합니다. 그리고 async에 해당하는 wait 구문을 사용하는 방법:

getNumber().then(data => {
 // got data
})
// =>
let data = await getNumber()

await를 실행하면 expression 뒤에 있는 Promise 실행 결과를 얻게 됩니다. 이는 콜백 결과를 얻기 위해 then을 호출하는 것과 동일합니다. 추신: 비동기/대기 지원이 그다지 높지 않은 경우 모든 사람은 유사한 효과를 얻기 위해 co와 유사한 일부 라이브러리와 결합된 생성기/수율을 사용하기로 선택했습니다.

async 함수 코드 실행은 동기식이며 결과 반환은 비동기식입니다

비동기 함수 이는 항상 Promise의 인스턴스를 반환하므로 비동기 함수를 호출할 때 내부 코드가 new Promise에 있음을 이해할 수 있으며 최종 반환 작업은 Resolve를 호출할 때와 동일합니다. Promise:

async function getNumber () {
 console.log('call getNumber()')
 return 1
}
getNumber().then(_ => console.log('resolved'))
console.log('done')
// 输出顺序:
// call getNumber()
// done
// resolved

Promise 내부의 Promise는 소화될 것입니다

즉, 다음과 같은 코드가 있다면:

function getNumber () {
 return new Promise(resolve => {
  resolve(Promise.resolve(1))
 })
}
getNumber().then(data => console.log(data)) // 1

위에서 말한 내용을 따르면 데이터를 얻을 수 있습니다. 또 다른 Promise 인스턴스인 Resolve에 전달된 값입니다.
그러나 실제로는 반환 값을 직접 얻습니다. 1. 즉, Promise가 Promise에서 반환되면 프로그램은 실제로 이 Promise를 실행하는 데 도움이 되며 내부 Promise 상태가 변경될 때와 같은 콜백을 트리거합니다. .
흥미로운 점:

function getNumber () {
 return new Promise(resolve => {
  resolve(Promise.reject(new Error('Test')))
 })
}
getNumber().catch(err => console.error(err)) // Error: Test

Resolve에서 거부를 전달하면 직접 catch를 사용하여 외부에서 모니터링할 수 있습니다.
이 방법은 비동기 함수에서 예외를 발생시키는 데 자주 사용됩니다.비동기 함수에서 예외를 발생시키는 방법:

async function getNumber () {
 return Promise.reject(new Error('Test'))
}
try {
 let number = await getNumber()
} catch (e) {
 console.error(e)
}

await 키워드를 잊지 마세요

await 키워드 추가를 잊어버린 경우, 코드 수준 아니요 오류가 보고되지만 우리가 받는 반환 값은 Promise입니다

let number = getNumber()
console.log(number) // Promise
사용할 때 wait 키워드를 꼭 기억하세요

let number = await getNumber()
console.log(number) // 1
모든 곳에 wait를 추가할 필요는 없습니다

코드 실행 중에 다음이 있습니다. 현재 모든 비동기 작업에 대기를 추가할 필요는 없습니다. 예를 들어 파일에 대한 작업은 다음과 같습니다.

fs의 모든 API가 우리에 의해 Promise 버전으로 변환되었다고 가정합니다.

let number = await getNumber()
console.log(number) // 1
await를 통해 파일을 연 다음 파일을 두 번 씁니다.

그러나 두 파일 쓰기 작업 전에 wait 키워드를 추가하지 않았습니다.

이것은 중복되기 때문에 이 파일에 텍스트 한 줄을 쓰고 싶다는 것을 API에 알리기만 하면 됩니다. 순서는 자연스럽게 fs에 의해 제어됩니다.
그런 다음 마지막에 대기를 사용하여 파일을 닫습니다.
위의 쓰기 프로세스를 실행하면 닫기 콜백이 트리거되지 않습니다.
즉, 콜백이 트리거된다는 것은 위의 두 가지 쓰기 단계가 완료되었음을 의미합니다.

관련되지 않은 여러 비동기 함수 호출을 병합

이제 사용자의 아바타와 사용자 세부 정보를 얻으려는 경우(정상적인 상황에서는 나타나지 않을 것 같지만 두 인터페이스입니다)

async function getUser () {
 let avatar = await getAvatar()
 let userInfo = await getUserInfo()
 return {
  avatar,
  userInfo
 }
}

这样的代码就造成了一个问题,我们获取用户信息的接口并不依赖于头像接口的返回值。
 但是这样的代码却会在获取到头像以后才会去发送获取用户信息的请求。
 所以我们对这种代码可以这样处理:

async function getUser () {
 let [avatar, userInfo] = await Promise.all([getAvatar(), getUserInfo()])
 return {
  avatar,
  userInfo
 }
}

这样的修改就会让getAvatar与getUserInfo内部的代码同时执行,同时发送两个请求,在外层通过包一层Promise.all来确保两者都返回结果。

让相互没有依赖关系的异步函数同时执行

一些循环中的注意事项

forEach

当我们调用这样的代码时:

async function getUsersInfo () {
 [1, 2, 3].forEach(async uid => {
  console.log(await getUserInfo(uid))
 })
}
function getuserInfo (uid) {
 return new Promise(resolve => {
  setTimeout(_ => resolve(uid), 1000)
 })
}
await getUsersInfo()

这样的执行好像并没有什么问题,我们也会得到1、2、3三条log的输出,但是当我们在await getUsersInfo()下边再添加一条console.log('done')的话,就会发现:

 我们会先得到done,然后才是三条uid的log,也就是说,getUsersInfo返回结果时,其实内部Promise并没有执行完。
 这是因为forEach并不会关心回调函数的返回值是什么,它只是运行回调。

不要在普通的for、while循环中使用await

使用普通的for、while循环会导致程序变为串行:

for (let uid of [1, 2, 3]) {
 let result = await getUserInfo(uid)
}

这样的代码运行,会在拿到uid: 1的数据后才会去请求uid: 2的数据

--------------------------------------------------------------------------------

关于这两种问题的解决方案:

目前最优的就是将其替换为map结合着Promise.all来实现:

await Promise.all([1, 2, 3].map(async uid => await getUserInfo(uid)))

这样的代码实现会同时实例化三个Promise,并请求getUserInfo

P.S. 草案中有一个await*,可以省去Promise.all

await Promise.all([1, 2, 3].map(async uid => await getUserInfo(uid)))

P.S. 为什么在使用Generator+co时没有这个问题

在使用koa1.x的时候,我们直接写yield [].map是不会出现上述所说的串行问题的看过co源码的小伙伴应该都明白,里边有这么两个函数(删除了其余不相关的代码):

function toPromise(obj) {
 if (Array.isArray(obj)) return arrayToPromise.call(this, obj);
 return obj;
}
function arrayToPromise(obj) {
 return Promise.all(obj.map(toPromise, this));
}

co是帮助我们添加了Promise.all的处理的(膜拜TJ大佬)。

总结

总结一下关于async函数编写的几个小提示:

1.使用return Promise.reject()在async函数中抛出异常
2.让相互之间没有依赖关系的异步函数同时执行
3.不要在循环的回调中/for、while循环中使用await,用map来代替它

相信看了本文案例你已经掌握了方法,更多精彩请关注php中文网其它相关文章!

推荐阅读:

怎样使用垃圾回收器

怎样使用Nodejs内存治理

怎样使用vue的toast弹窗组件

위 내용은 JS 비동기 기능을 최적화하는 방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.