>  기사  >  웹 프론트엔드  >  JS 비동기 프로그래밍 Promise, Generator, async/await

JS 비동기 프로그래밍 Promise, Generator, async/await

不言
不言원래의
2018-07-07 09:56:361590검색

이 글은 JS 비동기 프로그래밍에 관한 Promise, Generator, async/await를 주로 소개하고 있습니다. 이제 필요한 친구들이 참고할 수 있도록 공유하겠습니다.

JS Asynchronous 프로그래밍 (2) - Promise, Generator, async/await

지난 글에서는 비동기란 무엇인지, 비동기를 사용해야 하는 이유 등 JS 비동기 프로그래밍 관련 지식에 대해 이야기했습니다. 프로그래밍 및 브라우저에서 비동기 JS를 구현하는 방법.
마지막으로 여러 JS 비동기 프로그래밍 모드(콜백, 이벤트 및 게시/구독 모드)에 대해 이야기했습니다. 이 기사에서는 여러 다른 비동기 프로그래밍 모드에 대해 계속해서 자세히 살펴보겠습니다.

Promise

Promise는 ES6에서 도입된 비동기 프로그래밍 솔루션입니다. 실제로 ES6 이전에는 많은 비동기 도구 라이브러리가 이미 다양한 유사한 솔루션을 구현했으며 ES6에서는 이를 언어 표준에 작성하고 사용법을 통합했습니다. Promise는 콜백과 같은 중첩된 솔루션 문제를 해결하고 코드를 더 읽기 쉽게 만들어 동기 메서드를 작성할 때 기시감을 줍니다.

먼저 ES6에서 Promise의 사용법을 간단히 이해합시다

var p = new Promise(function async(resolve, reject){    // 这里是你的异步操作
    setTimeout(function(){        if(true){
            resolve(val);
        }else{
            reject(error);
        }
    }, 1000)
})

p.then(function(val){    console.log('resolve');
}, function(){    console.log('reject');
})

먼저 ES6에서는 Promise가 async 함수, 이 함수에는 두 개의 매개변수가 있으며, 각각은 성공과 실패의 두 가지 상태에 해당합니다. 우리는 해결 또는 거부를 실행할 수 있습니다. 다음 Action을 트리거하기 위해 서로 다른 시간에 then 메서드에서 함수를 실행합니다. async函数,此函数有两个参数,resolve、reject分别对应成功失败两种状态,我们可以选择在不同时候执行resolve或者reject去触发下一个动作,执行then方法里的函数。

我们可以简单对比下回调的写法和promise的写法的不同

对于传统回调写法来说,一般会写成这样

asyncFn1(function () {
  asyncFn2(function() {
    asyncFn3(function() {        // xxxxx
    });
  });
});

或者我们将各个回调函数拆出来独立来写以减少耦合,像是这样:

function asyncFn1(callback) {    return function() {        console.log('asyncFn1 run');
        setTimeout(function(){
            callback();
        }, 1000);
    }
}function asyncFn2(callback) {    return function(){        console.log('asyncFn2 run');
        setTimeout(function(){
            callback();
        }, 1000);
    }
}function normalFn3() {    console.log('normalFn3 run');
}

asyncFn1(asyncFn2(normalFn3))()

最后我们看下Promise的写法

function asyncFn1() {    
console.log('asyncFn1 run');    
return new Promise(function(resolve, reject) {
        setTimeout(function(){
            resolve();
        }, 1000)
    })
}function asyncFn2() {    
console.log('asyncFn2 run');    
return new Promise(function(resolve, reject) {
        setTimeout(function(){
            resolve();
        }, 1000)
    })
}function normalFn3() {    console.log('normalFn3 run');
}

asyncFn1().then(asyncFn2).then(normalFn3);

这样来看无论是第一种还是第二种写法,都会让人感到不是很直观,而Promise的写法更加直观和语义化。

Generator

Generator函数也是ES6提供的一种特殊的函数,其语法行为与传统函数完全不同。

我们先直观看个Generator实际的用法

function* oneGenerator() {  
yield 'Learn';  
yield 'In';  
return 'Pro';
}var g = oneGenerator();

g.next();   // {value: "Learn", done: false}g.next();   // {value: "In", done: false}g.next();   // {value: "Pro", done: true}

Generator函数是一种特殊的函数,他有这么几个特点:

  • 声明时需要在function后面加上*,并且配合函数里面yield

    콜백 작성 방식과 프라미스 작성 방식의 차이점을 간단히 비교해 보면 됩니다#🎜🎜##🎜🎜##🎜🎜#전통적인 콜백 작성의 경우 일반적으로 다음과 같이 작성합니다# 🎜🎜## 🎜🎜#
    // 使用Generator函数进行异步编程function* oneGenerator() {  yield asyncFn1();  yield asyncFn2();  yield normalFn3();
    }// 我们来对比一下PromiseasyncFn1().then(asyncFn2).then(normalFn3);
    #🎜🎜##🎜🎜#또는 다음과 같이 각 콜백 함수를 분리하고 독립적으로 작성하여 결합을 줄일 수도 있습니다. #🎜🎜##🎜🎜#
    var g;function asyncFn() {
        setTimeout(function(){
            g.next();
        }, 1000)
    }function normalFn() {    console.log('normalFn run');
    }function* oneGenerator() {  yield asyncFn();  return normalFn();
    }
    
    g = oneGenerator();
    
    g.next();// 这里在我调用next方法的时候执行了asyncFn函数// 然后我们的希望是在异步完成时自动去再调用g.next()来进行下面的操作,所以我们必须在上面asyncFn函数体内的写上g.next(); 这样才能正常运行。// 但其实这样是比较奇怪的,因为当我定义asyncFn的时候其实是不知道oneGenerator执行后叫什么名儿的,即使我们提前约定叫g,但这样asyncFn就太过于耦合了,不仅写法很奇怪而且耦合太大不利于扩展和重用。反正总而言之这种写法很不好。
    #🎜🎜##🎜🎜 #마지막으로 Promise가 어떻게 작성되는지 살펴보겠습니다 #🎜🎜##🎜🎜#
    // 如果我们想要去在异步执行完成时自动调用next就需要有一个钩子,回调函数的callback或者Promise的then。function autoGenerator(generator){  var g = generator();  function next(){    var res = g.next();  // {value: xxx, done: xxx}
    
        if (res.done) {        return res.value;
        }    if(typeof res.value === 'function'){    // 认为是回调
            res.value(next);
        }else if(typeof res.value === 'object' && typeof res.value.then === 'function'){     // 认为是promise
            res.value.then(function(){
                next();
            })
        }else{
            next();
        }
      }
    
      next();
    }// ----function asyncFn1(){    console.log('asyncFn1');    return new Promise(function(resolve){
            setTimeout(function(){
                resolve();
            }, 1000)
        })
    }function asyncFn2() {    console.log('asyncFn2');    return function(callback){
            setTimeout(function(){
                callback();
            }, 1000);
        }
    }function normalFn() {    console.log('normalFn');
    }function* oneGenerator() {  yield asyncFn1();  yield asyncFn2();  yield normalFn();
    }
    
    autoGenerator(oneGenerator);
    #🎜🎜# 이렇게 하면 첫 번째나 두 번째 작성 방법을 막론하고 사람들이 매우 직관적이지 않다는 느낌을 받게 됩니다. , 그리고 Promise를 작성하는 방식이 더 직관적이고 의미론적입니다. #🎜🎜##🎜🎜##🎜🎜#Generator#🎜🎜##🎜🎜##🎜🎜#Generator 함수도 ES6에서 제공하는 특수 함수로, 문법적 동작이 기존 함수와 완전히 다릅니다. #🎜🎜##🎜🎜#제너레이터의 실제 사용법을 먼저 살펴보겠습니다#🎜🎜#
    function asyncFn1(){    console.log('asyncFn1');    return new Promise(function(resolve){
            setTimeout(function(){
                resolve('123');
            }, 2000)
        })
    }function asyncFn2() {    console.log('asyncFn2');    return new Promise(function(resolve){
            setTimeout(function(){
                resolve('456');
            }, 2000)
        })
    }
    
    async function asyncFn () {    var a = await asyncFn1();    var b = await asyncFn2();    console.log(a,b)
    }
    
    asyncFn();// asyncFn1// asyncFn2// 123,456
    #🎜🎜#제너레이터 기능은 다음과 같은 몇 가지 특징을 가지고 있습니다. #🎜🎜##🎜🎜#
    • 함수 뒤에 *, yield 키워드를 사용하세요. #🎜🎜#

    • 在执行Generator函数的时候,其会返回一个Iterator遍历器对象,通过其next方法,将Generator函数体内的代码以yield为界分步执行

    • 具体来说当执行Generator函数时,函数并不会执行,而是需要调用Iterator遍历器对象的next方法,这时程序才会执行从头或者上一个yield之后到下一个yield或者return或者函数体尾部之间的代码,并且将yield后面的值,包装成json对象返回。就像上面的例子中的{value: xxx, done: xxx}

    • value取的yield或者return后面的值,否则就是undefined,done的值如果碰到return或者执行完成则返回true,否则返回false。

我们知道了简单的Generator函数的用法以后,我们来看下如何使用Generator函数进行异步编程。

首先我们先来看下使用Generator函数能达到怎样的效果。

// 使用Generator函数进行异步编程function* oneGenerator() {  yield asyncFn1();  yield asyncFn2();  yield normalFn3();
}// 我们来对比一下PromiseasyncFn1().then(asyncFn2).then(normalFn3);

我们可以看出使用Generator函数进行异步编程更像是在写同步任务,对比Promise少了很多次then方法的调用。

好,那么接下来我们就来看下如何实际使用Generator函数进行异步编程。

这里我要特别说明一下,事实上Generator函数不像Promise一样是专门用来解决异步处理而产生的,人们只是使用其特性来产出了一套异步的解决方案,所以使用Generator并不像使用Promise一样有一种开箱即用的感觉。其更像是在Promise或者回调这类的解决方案之上又封装了一层,让你可以像上面例子里一样去那么写。

我们还是具体来看下上面的例子,我们知道单写一个Generator是不能运行的对吧,我们需要执行他并且使用next方法来让他分步执行,那么什么时候去调用next呢?答案就是我们需要在异步完成时去调用next。我们来按照这个思路补全上面的例子。

var g;function asyncFn() {
    setTimeout(function(){
        g.next();
    }, 1000)
}function normalFn() {    console.log('normalFn run');
}function* oneGenerator() {  yield asyncFn();  return normalFn();
}

g = oneGenerator();

g.next();// 这里在我调用next方法的时候执行了asyncFn函数// 然后我们的希望是在异步完成时自动去再调用g.next()来进行下面的操作,所以我们必须在上面asyncFn函数体内的写上g.next(); 这样才能正常运行。// 但其实这样是比较奇怪的,因为当我定义asyncFn的时候其实是不知道oneGenerator执行后叫什么名儿的,即使我们提前约定叫g,但这样asyncFn就太过于耦合了,不仅写法很奇怪而且耦合太大不利于扩展和重用。反正总而言之这种写法很不好。

那么怎么解决呢,我们需要自己写个方法,能自动运行Generator函数,这种方法很简单在社区里有很多,最著名的就是大神TJ写的co模块,有兴趣的同学可以看下其源码实现。这里我们简单造个轮子:

// 如果我们想要去在异步执行完成时自动调用next就需要有一个钩子,回调函数的callback或者Promise的then。function autoGenerator(generator){  var g = generator();  function next(){    var res = g.next();  // {value: xxx, done: xxx}

    if (res.done) {        return res.value;
    }    if(typeof res.value === 'function'){    // 认为是回调
        res.value(next);
    }else if(typeof res.value === 'object' && typeof res.value.then === 'function'){     // 认为是promise
        res.value.then(function(){
            next();
        })
    }else{
        next();
    }
  }

  next();
}// ----function asyncFn1(){    console.log('asyncFn1');    return new Promise(function(resolve){
        setTimeout(function(){
            resolve();
        }, 1000)
    })
}function asyncFn2() {    console.log('asyncFn2');    return function(callback){
        setTimeout(function(){
            callback();
        }, 1000);
    }
}function normalFn() {    console.log('normalFn');
}function* oneGenerator() {  yield asyncFn1();  yield asyncFn2();  yield normalFn();
}

autoGenerator(oneGenerator);

这个方法我们简单实现了最核心的部分,有些判断可能并不严谨,但大家理解这个思路就可以了。有了这个方法,我们才可以方便的使用Generator函数进行异步编程。

Async/Await

如果你学会了Generator函数,对于Async函数就会很容易上手。你可以简单把Async函数理解成就是Generator函数+执行器。我们就直接上实例好了

function asyncFn1(){    console.log('asyncFn1');    return new Promise(function(resolve){
        setTimeout(function(){
            resolve('123');
        }, 2000)
    })
}function asyncFn2() {    console.log('asyncFn2');    return new Promise(function(resolve){
        setTimeout(function(){
            resolve('456');
        }, 2000)
    })
}

async function asyncFn () {    var a = await asyncFn1();    var b = await asyncFn2();    console.log(a,b)
}

asyncFn();// asyncFn1// asyncFn2// 123,456

当然async里实现的执行器肯定是跟咱们上面简单实现的有所不同,所以在用法上也会有些注意的点

  • 首先async函数的返回值是一个Promise对象,不像是generator函数返回的是Iterator遍历器对象,所以async函数执行后可以继续使用then等方法来继续进行下面的逻辑

  • await 뒤에는 일반적으로 Promise 객체가 옵니다. 비동기 함수가 실행되면 Wait가 발생한 후 후속 Promise 객체의 상태가 보류에서 해결로 변경될 때까지 기다린 다음 해결 매개변수를 반환하고 자동으로 실행을 계속합니다. 다음 wait 또는 End

  • await까지 비동기 함수와 중첩될 수도 있습니다.

비동기의 경우 예외 처리, 다중 비동기 병렬 실행 등과 같이 우리가 다루지 않은 지식 포인트가 여전히 많이 있습니다. 이 기사와 이전 기사는 주로 모든 사람이 비동기 프로그래밍에 대해 직관적으로 이해하기를 바랍니다. . 다양한 솔루션 간의 차이점과 장단점을 이해합니다. 제한된 공간과 에너지로 인해 우리가 다루지 않은 다른 지식 사항에 대해 관심이 있고 기회가 있다면 깊이 설명하기 위해 다른 기사를 작성하겠습니다.

위 내용은 이 글의 전체 내용입니다. 모든 분들의 학습에 도움이 되었으면 좋겠습니다. 더 많은 관련 내용은 PHP 중국어 홈페이지를 주목해주세요!

관련 권장 사항:

JS 비동기 프로그래밍 소개

Reflux 기본 소개

위 내용은 JS 비동기 프로그래밍 Promise, Generator, async/await의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

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