>웹 프론트엔드 >JS 튜토리얼 >Async 함수로 비동기 코드 단순화(JavaScript 개발 팁)

Async 함수로 비동기 코드 단순화(JavaScript 개발 팁)

PHPz
PHPz원래의
2017-04-23 09:49:581268검색

Promise는 JavaScript에서 처음 출시되었을 때 인터넷에서 인기를 얻었습니다. 이는 개발자가 콜백 지옥에서 벗어나고 여러 곳에서 JavaScript 개발자를 괴롭히는 비동기 문제를 해결하는 데 도움이 됩니다. 그러나 약속은 완벽하지 않습니다. 그들은 계속 콜백을 요청하지만 일부 복잡한 문제에 대해서는 여전히 약간 지저분하고 엄청나게 중복될 수 있습니다.

ES6(현재 ES2015)이 출시되면서 Promise 사양이 도입되고 수많은 라이브러리를 요청할 필요가 없어졌으며 생성기도 생겼습니다. 생성기는 함수 내에서 실행을 중지할 수 있습니다. 즉, 생성기는 다목적 함수로 래핑될 수 있으며 다음 코드 줄로 이동하기 전에 비동기 작업이 완료될 때까지 기다릴 수 있습니다. 갑자기 비동기 코드가 동기적으로 보이기 시작할 수 있습니다.

이것은 첫 번째 단계에 불과합니다. 올해 ES2017에 추가된 이후 비동기 기능이 표준화되었으며, 로컬 지원도 더욱 최적화되었습니다. 비동기 함수의 아이디어는 비동기 프로그래밍을 위한 생성기를 사용하여 고유한 의미와 구문을 제공하는 것입니다. 따라서 캡슐화된 유틸리티 기능을 얻기 위해 라이브러리를 사용할 필요가 없습니다. 이러한 기능은 뒤에서 처리되기 때문입니다.

기사에 나온 async/await 예제를 실행하려면 호환되는 브라우저가 필요합니다.

실행 호환

클라이언트 측에서 Chrome, Firefox 및 Opera는 비동기 기능을 잘 지원합니다.

Async 함수로 비동기 코드 단순화(JavaScript 개발 팁)

버전 7.6부터 Node.js는 기본적으로 async/await를 활성화합니다.

비동기 함수와 생성기의 비교

다음은 Q 라이브러리를 사용하여 비동기 프로그래밍에 생성기를 사용하는 예입니다.

var doAsyncOp = Q.async(function* () { 
 
  var val = yield asynchronousOperation(); 
 
  console.log(val); 
 
  return val; 
 
});

Q .async 장면 이후에 작업을 처리하는 래퍼 함수입니다. 여기서 *는 생성기 함수로서의 함수를 나타내고, 항복은 중지 함수를 나타내며 래퍼 함수로 대체됩니다. Q.async는 doAsyncOp처럼 값을 할당할 수 있는 함수를 반환한 다음 이를 호출합니다.

ES7의 새로운 구문은 더 간결해졌습니다.

async function doAsyncOp () { 
 
  var val = await asynchronousOperation();      
 
  console.log(val); 
 
  return val; 
 
};

큰 차이는 없습니다. 캡슐화된 함수 및* 기호를 사용하려면 대신 async 키워드를 사용하세요. 항복 키워드도 대기로 대체되었습니다. 이 두 예제는 실제로 동일한 작업을 수행합니다. 즉, asynchronousOperation이 완료된 후 값을 val에 할당한 다음 결과를 출력하고 반환합니다. Promise를 비동기 함수로 변환

Vanilla Promises를 사용하면 이전 예제는 어떻게 보일까요?

function doAsyncOp () { 
 
  return asynchronousOperation().then(function(val) { 
 
    console.log(val); 
 
    return val; 
 
  }); 
 
};

여기서는 동일한 코드 줄, 하지만 그 이유는 전달된

콜백 함수

가 많은 추가 코드를 추가하기 때문입니다. 또 다른 성가심은 두 개의 반환 키워드입니다. Promise를 사용하는 함수가 정확히 무엇을 반환하는지 파악하기 어렵기 때문에 이것은 항상 나를 괴롭히는 것이었습니다. 보시다시피 이 함수는 val에 할당될 약속을 반환합니다. 생성기 및 비동기 함수 예제가 무엇을 하는지 추측해 보세요. 이 함수에서 무엇을 반환하든 비밀리에 이를 해결하는 약속을 반환합니다. 값. 값을 전혀 반환하지 않으면 반환한 Promise는 암시적으로 undefound로 해석됩니다.

연결된 작업

Promise가 인기 있는 이유 중 하나는 포함된 콜백을 피하면서 연결된 호출에서 여러 비동기 작업을 연결할 수 있다는 것입니다. 그러나 이 점에서는 비동기 기능이 Promise보다 훨씬 좋습니다.

다음은 Promise를 사용하여 체인 작업을 수행하는 방법을 보여줍니다(보여주기 위해 asynchronousOperation을 여러 번 실행하기만 하면 됩니다).

function doAsyncOp() { 
 
  return asynchronousOperation() 
 
    .then(function(val) { 
 
      return asynchronousOperation(val); 
 
    }) 
 
    .then(function(val) { 
 
      return asynchronousOperation(val); 
 
    }) 
 
    .then(function(val) { 
 
      return asynchronousOperation(val); 
 
    }); 
 
}

async 함수를 사용하면 동기 코드를 작성하는 것처럼 asynchronousOperation만 호출하면 됩니다.

async function doAsyncOp () { 
 
  var val = await asynchronousOperation(); 
 
  val = await asynchronousOperation(val); 
 
  val = await asynchronousOperation(val); 
 
  return await asynchronousOperation(val); 
 
};

사용할 필요도 없습니다. 최종 반환 문 Await에서는 사용 여부에 관계없이 처리 가능한 최종 값이 포함된 Promise를 반환합니다.

동시성 작업

Promise에는 또 다른 훌륭한 기능이 있습니다. 여러 비동기 작업을 동시에 수행하고 모든 작업이 완료될 때까지 기다린 후 계속할 수 있습니다.

기타

이벤트 . ES2015 사양에서는 이를 수행하는 데 사용되는 Promise.all()을 제공합니다. 예는 다음과 같습니다.

function doAsyncOp() { 
 
  return Promise.all([ 
 
    asynchronousOperation(), 
 
    asynchronousOperation() 
 
  ]).then(function(vals) { 
 
    vals.forEach(console.log); 
 
    return vals; 
 
  }); 
 
}

Promise.all()은 비동기 함수로도 사용할 수 있습니다.

async function doAsyncOp() { 
 
  var vals = await Promise.all([ 
 
    asynchronousOperation(), 
 
    asynchronousOperation() 
 
  ]); 
 
  vals.forEach(console.log.bind(console)); 
 
  return vals; 
 
}

여기서는 Promise.all을 사용해도 코드는 여전히 명확합니다.

거절 처리

Promises 可以被接受(resovled)也可以被拒绝(rejected)。被拒绝的 Promise 可以通过一个函数来处理,这个处理函数要传递给 then,作为其第二个参数,或者传递给 catch 方法。现在我们没有使用 Promise API 中的方法,应该怎么处理拒绝?可以通过 try 和 catch 来处理。使用 async 函数的时候,拒绝被当作错误来传递,这样它们就可以通过 JavaScript 本身支持的错误处理代码来处理。

function doAsyncOp() { 
 
  return asynchronousOperation() 
 
    .then(function(val) { 
 
      return asynchronousOperation(val); 
 
    }) 
 
    .then(function(val) { 
 
      return asynchronousOperation(val); 
 
    }) 
 
    .catch(function(err) { 
 
      console.error(err); 
 
    }); 
 
}

 

这与我们链式处理的示例非常相似,只是把它的最后一环改成了调用 catch。如果用 async 函数来写,会像下面这样。

async function doAsyncOp () { 
 
  try { 
 
    var val = await asynchronousOperation(); 
 
    val = await asynchronousOperation(val); 
 
    return await asynchronousOperation(val); 
 
  } catch (err) { 
 
    console.err(err); 
 
  } 
 
};

 

它不像其它往 async 函数的转换那样简洁,但是确实跟写同步代码一样。如果你在这里不捕捉错误,它会延着调用链一直向上抛出,直到在某处被捕捉处理。如果它一直未被捕捉,它最终会中止程序并抛出一个运行时错误。Promise 以同样的方式运作,只是拒绝不必当作错误来处理;它们可能只是一个说明错误情况的字符串。如果你不捕捉被创建为错误的拒绝,你会看到一个运行时错误,不过如果你只是使用一个字符串,会失败却不会有输出。

中断 Promise

拒绝原生的 Promise,只需要使用 Promise 构建函数中的 reject 就好,当然也可以直接抛出错误——在 Promise 的构造函数中,在 then 或 catch 的回调中抛出都可以。如果是在其它地方抛出错误,Promise 就管不了了。

这里有一些拒绝 Promise 的示例:

function doAsyncOp() { 
 
  return new Promise(function(resolve, reject) { 
 
    if (somethingIsBad) { 
 
      reject("something is bad"); 
 
    } 
 
    resolve("nothing is bad"); 
 
  }); 
 
} 
 
  
 
/*-- or --*/ 
 
  
 
function doAsyncOp() { 
 
  return new Promise(function(resolve, reject) { 
 
    if (somethingIsBad) { 
 
      reject(new Error("something is bad")); 
 
    } 
 
    resolve("nothing is bad"); 
 
  }); 
 
} 
 
  
 
/*-- or --*/ 
 
  
 
function doAsyncOp() { 
 
  return new Promise(function(resolve, reject) { 
 
    if (somethingIsBad) { 
 
      throw new Error("something is bad"); 
 
    } 
 
    resolve("nothing is bad"); 
 
  }); 
 
}

 

一般来说,最好使用 new Error,因为它会包含错误相关的其它信息,比如抛出位置的行号,以及可能会有用的调用栈。

这里有一些抛出 Promise 不能捕捉的错误的示例:

function doAsyncOp() { 
 
  // the next line will kill execution 
 
  throw new Error("something is bad"); 
 
  return new Promise(function(resolve, reject) { 
 
    if (somethingIsBad) { 
 
      throw new Error("something is bad"); 
 
    } 
 
    resolve("nothing is bad"); 
 
  }); 
 
} 
 
  
 
// assume `doAsyncOp` does not have the killing error 
 
function x() { 
 
  var val = doAsyncOp().then(function() { 
 
    // this one will work just fine 
 
    throw new Error("I just think an error should be here"); 
 
  }); 
 
  // this one will kill execution 
 
  throw new Error("The more errors, the merrier"); 
 
  return val; 
 
}

 

在 async 函数的 Promise 中抛出错误就不会产生有关范围的问题——你可以在 async 函数中随时随地抛出错误,它总会被 Promise 抓住:

async function doAsyncOp() { 
 
  // the next line is fine 
 
  throw new Error("something is bad"); 
 
  if (somethingIsBad) { 
 
    // this one is good too 
 
    throw new Error("something is bad"); 
 
  } 
 
  return "nothing is bad"; 
 
}  
 
  
 
// assume `doAsyncOp` does not have the killing error 
 
async function x() { 
 
  var val = await doAsyncOp(); 
 
  // this one will work just fine 
 
  throw new Error("I just think an error should be here"); 
 
  return val; 
 
}

 

当然,我们永远不会运行到 doAsyncOp 中的第二个错误,也不会运行到 return 语句,因为在那之前抛出的错误已经中止了函数运行。

问题

如果你刚开始使用 async 函数,需要小心嵌套函数的问题。比如,如果你的 async 函数中有另一个函数(通常是回调),你可能认为可以在其中使用 await ,但实际不能。你只能直接在 async 函数中使用 await 。

比如,这段代码无法运行:

async function getAllFiles(fileNames) { 
 
  return Promise.all( 
 
    fileNames.map(function(fileName) { 
 
      var file = await getFileAsync(fileName); 
 
      return parse(file); 
 
    }) 
 
  ); 
 
}

 

第 4 行的 await 无效,因为它是在一个普通函数中使用的。不过可以通过为回调函数添加 async 关键字来解决这个问题。

async function getAllFiles(fileNames) { 
 
  return Promise.all( 
 
    fileNames.map(async function(fileName) { 
 
      var file = await getFileAsync(fileName); 
 
      return parse(file); 
 
    }) 
 
  ); 
 
}

 

你看到它的时候会觉得理所当然,即便如此,仍然需要小心这种情况。

也许你还想知道等价的使用 Promise 的代码:

function getAllFiles(fileNames) { 
 
  return Promise.all( 
 
    fileNames.map(function(fileName) { 
 
      return getFileAsync(fileName).then(function(file) { 
 
        return parse(file); 
 
      }); 
 
    }) 
 
  ); 
 
}

 

接下来的问题是关于把 async 函数看作同步函数。需要记住的是,async 函数内部的的代码是同步运行的,但是它会立即返回一个 Promise,并继续运行外面的代码,比如:

var a = doAsyncOp(); // one of the working ones from earlier 
 
console.log(a); 
 
a.then(function() { 
 
  console.log("`a` finished"); 
 
}); 
 
console.log("hello"); 
 
  
 
/* -- will output -- */ 
 
Promise Object 
 
hello 
 
`a` finished

 

你会看到 async 函数实际使用了内置的 Promise。这让我们思考 async 函数中的同步行为,其它人可以通过普通的 Promise API 调用我们的 async 函数,也可以使用它们自己的 async 函数来调用。

如今,更好的异步代码!

即使你本身不能使用异步代码,你也可以进行编写或使用工具将其编译为 ES5。 异步函数能让代码更易于阅读,更易于维护。 只要我们有 source maps,我们可以随时使用更干净的 ES2017 代码。

有许多可以将异步功能(和其他 ES2015+功能)编译成 ES5 代码的工具。 如果您使用的是 Babel,这只是安装 ES2017 preset 的例子。

위 내용은 Async 함수로 비동기 코드 단순화(JavaScript 개발 팁)의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

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