대부분의 JavaScript 개발자에게 비동기 기능은 새로운 것이며 그 개발은 오랜 여정을 거쳐 왔습니다. 그래서 이 글에서는 자바스크립트 비동기 함수의 개발 역사를 정리하고 요약하고자 한다. 얼마 전까지만 해도 비동기를 구현하기 위해 콜백 함수만 작성할 수 있었는데, 그러다가 Promise/A+ 표준이 나왔고, 그러다가 생성기 기능이 등장했습니다. 미래는 분명히 비동기 기능입니다.
이제 수년간의 JavaScript 비동기 함수 개발을 검토해 보겠습니다.
모든 것은 콜백 함수에서 시작되어야 할 것 같습니다.
우리가 알고 있듯이 JavaScript에서 비동기 프로그래밍은 JavaScript 언어의 일급 시민 함수를 통해서만 수행될 수 있습니다. 이 방법은 우리가 함수를 다른 함수의 매개변수로 사용할 수 있으며, 전달된 함수(예: 콜백 함수)를 이 함수 내에서 호출할 수 있습니다. 이것이 콜백 함수가 탄생한 이유입니다. 함수를 다른 함수(현재는 고차 함수라고 함)에 매개변수로 전달하면 함수 내부에서 이 함수를 호출하여 해당 작업을 완료할 수 있습니다. 콜백 함수에는 반환 값이 없으며(반환을 사용하지 마세요) 함수 내에서 특정 작업을 수행하는 데만 사용됩니다. 예를 보세요:
Something.save(function(err) { if (err) { //error handling return; // 没有返回值 } console.log('success'); });
위의 예에서는 Node.js 자체의 모든 핵심 모듈의 특징 중 하나이기도 한 오류 우선 콜백 함수(오류 우선 콜백)를 보여줍니다. Node.js NPM 저장소에 있는 대부분의 모듈은 작성 시 이 기능을 따릅니다.
콜백 함수의 과도한 사용으로 인한 문제점:
코드를 합리적으로 정리하지 못하면 콜백 지옥(콜백 지옥)이 발생하기 매우 쉽습니다. 다른 사람들이 이해하기 어려운 코드입니다.
오류 처리 코드를 놓치기 쉽습니다.
return 문을 사용하여 값을 반환할 수 없으며, throw 키워드를 사용할 수 없습니다.
이러한 이유로 JavaScript 세계에서 우리는 항상 비동기 JavaScript 개발을 더 쉽게 만들 수 있는 실행 가능한 솔루션을 찾고 있습니다.
가능한 솔루션 중 하나는 비동기 모듈입니다. 콜백 함수를 오랫동안 다뤄봤다면 자바스크립트에서 무언가를 병렬이나 직렬로 실행시키거나 심지어 비동기 함수를 사용하여 배열에 매핑(mapping)하려는 경우 어떻게 해야 하는지 깊이 이해할 수 있을 것입니다. complex는 비동기 함수를 사용하는 요소입니다. 따라서 이러한 문제를 해결하기 위해 비동기 모듈을 작성해 준 Caolan McMahon에게 감사드립니다.
비동기 모듈을 사용하면 다음과 같은 방법으로 쉽게 코드를 작성할 수 있습니다.
async.map([1, 2, 3], AsyncSquaringLibrary.square, function(err, result){ // result will be [1, 4, 9] });
비동기 모듈은 어느 정도 편리함을 제공하지만 여전히 간단하지 않고 코드도 엉성합니다. 읽기 쉬우므로 Promise가 나타납니다.
현재 JavaScript 비동기 표준은 2012년으로 거슬러 올라가며 ES6까지는 사용할 수 없었습니다. 그러나 Promise라는 용어는 JavaScript 커뮤니티에서 만들어지지 않았습니다. . 이 용어는 1976년 Daniel P.friedman이 출판한 기사에서 유래되었습니다.
프라미스는 비동기 작업의 최종 결과를 나타냅니다.
이제 Promise를 사용하여 위 코드로 완료한 작업을 완료합니다. Promise 스타일 코드는 다음과 같습니다.
Something.save() .then(function() { console.log('success'); }) .catch(function() { //error handling })
Promise에서도 콜백 함수가 사용되는 것을 확인할 수 있습니다. 콜백 함수는 Promise가 각각 충족되고 거부될 때 실행되는 then 및 catch 메소드 모두에 전달됩니다. Promise 함수의 또 다른 장점은 일련의 작업을 완료하기 위해 연결될 수 있다는 것입니다. 예를 들어 다음과 같은 코드를 작성할 수 있습니다.
saveSomething() .then(updateOtherthing) .then(deleteStuff) .then(logResults);
미리 만들어진 Promise가 없으면 일부 Promise 라이브러리를 사용해야 할 수도 있습니다. 가장 많이 사용되는 선택은 bluebird를 사용하는 것입니다. 이러한 라이브러리는 기본 솔루션보다 더 많은 기능을 제공할 수 있으며 Promise/A+ 표준에 지정된 기능으로 제한되지 않습니다.
그런데 설탕법을 사용하는 건 어때요? 먼저 Promise: The Extension Problem 기사를 읽어보는 것이 좋습니다. Promise에 대한 자세한 내용은 Promise/A+ 표준을 참조하세요.
대부분의 라이브러리가 콜백 인터페이스만 노출한다면 Promise를 어떻게 사용해야 합니까?
지금 해야 할 일은 매우 간단합니다. Promise를 사용하여 콜백이 포함된 함수 호출 본문을 래핑하는 것뿐입니다. 예:
콜백 스타일 코드는 다음과 같습니다.
function saveToTheDb(value) { db.values.insert(value, function (err, user) { if (err) throw err; // todo: insert user to db }); }
이제 Promise 스타일 호출을 지원하는 코드로 변경합니다.
function saveToTheDb(value) { return new Promise(function(resolve, reject) { db.values.insert(value, function(err, user) { // remember error first if (err) { return reject(err); // don't forget to return here } resolve(user); }) } }
이미 꽤 많은 라이브러리가 있습니다. 또는 프레임워크는 두 메서드를 동시에 지원합니다. 즉, 콜백 스타일과 Promise 스타일 API 인터페이스를 모두 제공합니다. 따라서 이제 외부 세계에도 라이브러리를 제공하려는 경우 두 인터페이스를 동시에 제공하는 것이 가장 좋습니다. 이 목적을 달성하려면 다음 방법을 쉽게 사용할 수 있습니다.
function foo(cb) { if (cb) { return cb(); } return new Promise(function (resolve, reject) { }); }
或者更简单些,你可以从只提供Promise风格的接口开始后,并使用诸如 callbackify这样的工具来达到向后兼容的目的。其实Callbackify所做的工作和上面的代码片段类似,但在实现上使用了一个更通用的方法, 我建议你可以去阅读Callbackify的源代码。
JavaScript 生成器是个相对较新的概念, 它是ES6(也被称为ES2015)的新特性。想象下面这样的一个场景:
当你在执行一个函数的时候,你可以在某个点暂停函数的执行,并且做一些其他工作,然后再返回这个函数继续执行, 甚至是携带一些新的值,然后继续执行。
上面描述的场景正是JavaScript生成器函数所致力于解决的问题。当我们调用一个生成器函数的时候,它并不会立即执行, 而是需要我们手动的去执行迭代操作(next方法)。也就是说,你调用生成器函数,它会返回给你一个迭代器。迭代器会遍历每个中断点。
function* foo () { var index = 0; while (index < 2) { yield index++; //暂停函数执行,并执行yield后的操作 } } var bar = foo(); // 返回的其实是一个迭代器 console.log(bar.next()); // { value: 0, done: false } console.log(bar.next()); // { value: 1, done: false } console.log(bar.next()); // { value: undefined, done: true }
更进一步的,如果你想更轻松的使用生成器函数来编写异步JavaScript代码,我们可以使用 co 这个库,co是著名的tj大神写的。
Co是一个为Node.js和浏览器打造的基于生成器的流程控制工具,借助于Promise,你可以使用更加优雅的方式编写非阻塞代码。
使用co,前面的示例代码,我们可以使用下面的代码来改写:
co(function* (){ yield Something.save(); }).then(function() { // success }) .catch(function(err) { //error handling });
你可能会问:如何实现并行操作呢?答案可能比你想象的简单,如下(其实它就是Promise.all而已):
yield [Something.save(), Otherthing.save()];
在ES7(还未正式标准化)中引入了Async函数的概念,目前如果你想要使用的话,只能借助于babel 这样的语法转换器将其转为ES5代码。(提醒一点:我们现在讨论的是async关键字,而不是NPM中的async包)。
简而言之,使用async关键字,你可以轻松地达成之前使用生成器和co函数所做到的工作。当然,除了hack之外。
也许你会问,是否在ES7中有了async关键字,yield就变得不是那么重要了?
实际上,使用yield实现异步也不过是一种hack罢了,yield意味着懒次序(lazy sequences)和迭代器。 而await能够完美的分离这两点,首先让yield用于其最初的目的,其次使用await来执行异步操作。
在这背后,async函数实际使用的是Promise,也就是为什么async函数会返回一个Promise的原因。
因此,我们使用async函数来完成类似于前面代码所完成的工作,可以使用下面这样的方式来重新编写代码:
async function save(Something) { try { await Something.save(); // 等待await后面的代码执行完,类似于yield } catch (ex) { //error handling } console.log('success'); }
正如你看到的那样,使用async函数,你需要在函数声明的最前面加上async关键字。这之后,你可以在函数内部使用await关键字了,作用和之前的yield作用是类似的。
使用async函数完成并行任务与yiled的方式非常的相似,唯一不同的是,此时Promise.all不再是隐式的,你需要显示的调用它:
async function save(Something) { await Promise.all[Something.save(), Otherthing.save()] }
Koa也支持async函数,如果你也在使用koa,那么你现在就可以借助babel使用这一特性了。
import koa from koa; let app = koa(); app.experimental = true; app.use(async function (){ this.body = await Promise.resolve('Hello Reader!') }) app.listen(3000);
위 내용은 JavaScript 비동기 함수의 개발 역사와 코드 예제에 대한 자세한 소개의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!