이번에는 js 비동기 함수 작성 단계에 대해 자세히 설명하겠습니다. js 비동기 함수 작성 시 주의사항은 무엇인가요? 실제 사례를 살펴보겠습니다.
2018년 5월이 되었고 4.x 버전의 node도 유지 관리가 중단되었습니다. 우리 서비스 중 하나도 8.x로 전환되었습니다. 현재 koa2.x로 마이그레이션하고 이전 생성기를 모두 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
let number = await getNumber() console.log(number) // 1
let number = await getNumber() console.log(number) // 1
이것은 중복되기 때문에 이 파일에 텍스트 한 줄을 쓰고 싶다는 것을 API에 알리기만 하면 됩니다. 순서는 자연스럽게 fs에 의해 제어됩니다.
그런 다음 마지막에 대기를 사용하여 파일을 닫습니다.
위의 쓰기 프로세스를 실행하면 닫기 콜백이 트리거되지 않습니다.
즉, 콜백이 트리거된다는 것은 위의 두 가지 쓰기 단계가 완료되었음을 의미합니다.
合并多个不相干的async函数调用
如果我们现在要获取一个用户的头像和用户的详细信息(而这是两个接口 虽说一般情况下不太会出现)
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中文网其它相关文章!
推荐阅读:
위 내용은 JS 비동기 함수 작성 단계에 대한 자세한 설명의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!