>  기사  >  웹 프론트엔드  >  JavaScript 비동기 함수의 개발 역사와 코드 예제에 대한 자세한 소개

JavaScript 비동기 함수의 개발 역사와 코드 예제에 대한 자세한 소개

黄舟
黄舟원래의
2017-03-11 15:12:141659검색

대부분의 JavaScript 개발자에게 비동기 기능은 새로운 것이며 그 개발은 오랜 여정을 거쳐 왔습니다. 그래서 이 글에서는 자바스크립트 비동기 함수의 개발 역사를 정리하고 요약하고자 한다. 얼마 전까지만 해도 비동기를 구현하기 위해 콜백 함수만 작성할 수 있었는데, 그러다가 Promise/A+ 표준이 나왔고, 그러다가 생성기 기능이 등장했습니다. 미래는 분명히 비동기 기능입니다.

이제 수년간의 JavaScript 비동기 함수 개발을 검토해 보겠습니다.

콜백 함수 콜백

모든 것은 콜백 함수에서 시작되어야 할 것 같습니다.

비동기 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가 나타납니다.

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的源代码。

生成器Generators/ yield

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()];

Async/ await

在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(&#39;success&#39;);  
}

正如你看到的那样,使用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(&#39;Hello Reader!&#39;)  
})  

app.listen(3000);

위 내용은 JavaScript 비동기 함수의 개발 역사와 코드 예제에 대한 자세한 소개의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

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