>  기사  >  웹 프론트엔드  >  Promise, Generator 및 Async의 차이점에 대한 간략한 분석

Promise, Generator 및 Async의 차이점에 대한 간략한 분석

青灯夜游
青灯夜游앞으로
2022-02-09 10:48:351847검색

Promise 및 Async/await 함수는 모두 JavaScript의 비동기 문제를 해결하는 데 사용됩니다. 그렇다면 두 함수의 차이점은 무엇인가요? 다음 기사에서는 Promise, Generator 및 Async의 차이점을 소개합니다. 도움이 되길 바랍니다.

Promise, Generator 및 Async의 차이점에 대한 간략한 분석

우리는 JavaScript의 비동기 문제를 해결하기 위해 PromiseAsync/await 함수가 사용된다는 것을 알고 있습니다. >Promise는 비동기 처리를 처리하고, Generator는 비동기 처리를 처리하고, Async/await는 비동기 처리를 처리합니다. 각 기술 업데이트를 통해 JavaScript는 비동기 처리를 보다 효율적으로 처리합니다. 현재의 관점에서 볼 때 Async/await는 비동기 처리를 위한 궁극적인 솔루션으로 간주되어 JS의 비동기 처리를 점점 더 동기 작업과 유사하게 만듭니다. 비동기 프로그래밍의 가장 높은 상태는 비동기 여부에 대해 걱정할 필요가 없다는 것입니다. PromiseAsync/await函数都是用来解决JavaScript中的异步问题的,从最开始的回调函数处理异步,到Promise处理异步,到Generator处理异步,再到Async/await处理异步,每一次的技术更新都使得JavaScript处理异步的方式更加优雅,从目前来看,Async/await被认为是异步处理的终极解决方案,让JS的异步处理越来越像同步任务。异步编程的最高境界,就是根本不用关心它是不是异步

异步解决方案的发展历程

1.回调函数

从早期的Javascript代码来看,在ES6诞生之前,基本上所有的异步处理都是基于回调函数函数实现的,你们可能会见过下面这种代码:

ajax('aaa', () => {
    // callback 函数体
    ajax('bbb', () => {
        // callback 函数体
        ajax('ccc', () => {
            // callback 函数体
        })
    })
})

没错,在ES6出现之前,这种代码可以说是随处可见。它虽然解决了异步执行的问题,可随之而来的是我们常听说的回调地狱问题:

  • 没有顺序可言:嵌套函数执行带来的是调试困难,不利于维护与阅读
  • 耦合性太强:一旦某一个嵌套层级有改动,就会影响整个回调的执行

所以,为了解决这个问题,社区最早提出和实现了Promise,ES6将其写进了语言标准,统一了用法。

2.Promise

Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它就是为了解决回调函数产生的问题而诞生的。

有了Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise对象提供统一的接口,使得控制异步操作更加容易。

所以上面那种回调函数的方式我们可以改成这样:(前提是ajax已用Promise包装)

ajax('aaa').then(res=>{
  return ajax('bbb')
}).then(res=>{
  return ajax('ccc')
})

通过使用Promise来处理异步,比以往的回调函数看起来更加清晰了,解决了回调地狱的问题,Promisethen的链式调用更能让人接受,也符合我们同步的思想。

但Promise也有它的缺点:

  • Promise的内部错误使用try catch捕获不到,只能只用then的第二个回调或catch来捕获
let pro
try{
    pro = new Promise((resolve,reject) => {
        throw Error('err....')
    })
}catch(err){
    console.log('catch',err) // 不会打印
}
pro.catch(err=>{
    console.log('promise',err) // 会打印
})
  • Promise一旦新建就会立即执行,无法取消

之前写过一篇,讲解了Promise如何使用以及内部实现原理。对Promise还不太理解的同学可以看看~

从如何使用到如何实现一个Promise

https://juejin.cn/post/7051364317119119396

3.Generator

Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同。Generator 函数将 JavaScript 异步编程带入了一个全新的阶段。

声明

与函数声明类似,不同的是function关键字与函数名之间有一个星号,以及函数体内部使用yield表达式,定义不同的内部状态(yield在英语里的意思就是“产出”)。

function* gen(x){
 const y = yield x + 6;
 return y;
}
// yield 如果用在另外一个表达式中,要放在()里面
// 像上面如果是在=右边就不用加()
function* genOne(x){
  const y = `这是第一个 yield 执行:${yield x + 1}`;
 return y;
}

执行

const g = gen(1);
//执行 Generator 会返回一个Object,而不是像普通函数返回return 后面的值
g.next() // { value: 7, done: false }
//调用指针的 next 方法,会从函数的头部或上一次停下来的地方开始执行,直到遇到下一个 yield 表达式或return语句暂停,也就是执行yield 这一行
// 执行完成会返回一个 Object,
// value 就是执行 yield 后面的值,done 表示函数是否执行完毕
g.next() // { value: undefined, done: true }
// 因为最后一行 return y 被执行完成,所以done 为 true

调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,也就是遍历器对象(Iterator Object)。下一步,必须调用遍历器对象的next方法,使得指针移向下一个状态。

所以上面的回调函数又可以写成这样:

function *fetch() {
    yield ajax('aaa')
    yield ajax('bbb')
    yield ajax('ccc')
}
let gen = fetch()
let res1 = gen.next() // { value: 'aaa', done: false }
let res2 = gen.next() // { value: 'bbb', done: false }
let res3 = gen.next() // { value: 'ccc', done: false }
let res4 = gen.next() // { value: undefined, done: true } done为true表示执行结束

由于 Generator 函数返回的遍历器对象,只有调用next方法才会遍历下一个内部状态,所以其实提供了一种可以暂停执行的函数。yield表达式就是暂停标志。

遍历器对象的next方法的运行逻辑如下。

(1)遇到yield表达式,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性值。

(2)下一次调用next方法时,再继续往下执行,直到遇到下一个yield

비동기 솔루션 개발 역사

1. 콜백 기능 Strong>🎜🎜초기 Javascript 코드로 볼 때 ES6가 탄생하기 이전에는 기본적으로 모든 비동기 처리가 콜백 함수를 기반으로 구현되었습니다. 🎜
function* foo(x) {
  var y = 2 * (yield (x + 1));
  var z = yield (y / 3);
  return (x + y + z);
}

var a = foo(5);
a.next() // Object{value:6, done:false}
a.next() // Object{value:NaN, done:false}
a.next() // Object{value:NaN, done:true}

var b = foo(5);
b.next() // { value:6, done:false }
b.next(12) // { value:8, done:false }
b.next(13) // { value:42, done:true }
🎜예, ES6에서 나타났습니다. 이전에는 이런 코드가 있었습니다. 코드는 어디에서나 볼 수 있었습니다. 비동기 실행 문제를 해결했지만 우리가 자주 듣는 콜백 지옥 문제가 뒤따릅니다. 🎜
  • 순서가 전혀 없습니다. 중첩된 함수 실행이 어렵습니다. 디버깅에 적합하며 유지 관리 및 읽기에 도움이 되지 않습니다.
  • 결합이 너무 강합니다. 특정 중첩 수준이 변경되면 전체 콜백 실행에 영향을 미칩니다.
🎜 따라서 이 문제를 해결하기 위해 커뮤니티에서는 먼저 Promise를 제안하고 구현했으며, ES6에서는 이를 언어 표준으로 작성하여 사용법을 통일했습니다.
🎜🎜2.Promise🎜🎜Promise는 비동기 프로그래밍에 대한 솔루션으로, 기존 솔루션보다 뛰어납니다. 콜백 함수 및 이벤트 - 더욱 합리적이고 강력해졌습니다. 콜백 함수로 인해 발생하는 문제를 해결하기 위해 탄생했습니다. 🎜🎜 Promise 개체를 사용하면 비동기 작업을 동기 작업 프로세스로 표현할 수 있어 중첩된 콜백 함수 레이어를 피할 수 있습니다. 또한 Promise 개체는 통합 인터페이스를 제공하여 비동기 작업을 더 쉽게 제어할 수 있습니다. 🎜🎜그래서 위의 콜백 함수를 다음과 같이 변경할 수 있습니다. (ajax가 Promise로 래핑된 경우)🎜
async function fetch() {
  	await ajax('aaa')
    await ajax('bbb')
    await ajax('ccc')
}
// 但这是在这三个请求有相互依赖的前提下可以这么写,不然会产生性能问题,因为你每一个请求都需要等待上一次请求完成后再发起请求,如果没有相互依赖的情况下,建议让它们同时发起请求,这里可以使用Promise.all()来处理
🎜 Promise를 사용하여 비동기식을 처리하면 이전 콜백 함수보다 더 우아해 보입니다. 명확하고 콜백 지옥 문제를 해결합니다. Promisethen 체인 호출이 더 수용 가능하며 동기화에 대한 우리의 아이디어와 일치합니다. 🎜🎜그러나 Promise에도 단점이 있습니다. 🎜
  • Promise의 내부 오류는 try catch를 사용하여 잡을 수 없으며 를 사용한 다음에만 사용할 수 있습니다. 의 두 번째 콜백 또는 캡처를 위한 catch
async function fn() {
  return 'async'
}
fn().then(res => {
  console.log(res) // 'async'
})
  • Promise가 생성되면 즉시 실행되며 취소할 수 없습니다.
  • ul>🎜저는 이전에 Promise 사용 방법과 내부 구현 원칙을 설명하는 기사를 작성했습니다. Promise를 잘 모르는 학생들도 보세요~🎜
    🎜Promise 사용법부터 구현 방법까지🎜🎜https://juejin.cn/post/7051364317119119396🎜
    🎜 3.Generator🎜🎜Generator 함수는 ES6에서 제공하는 비동기 프로그래밍 솔루션입니다. 구문과 동작은 완전히 동일합니다. 전통적인 기능과 다릅니다. Generator 함수는 JavaScript 비동기 프로그래밍을 완전히 새로운 수준으로 끌어올립니다. 🎜🎜선언🎜🎜은 function 키워드와 함수 이름을 제외하고 함수 선언과 유사합니다. 사이에는 별표가 있으며 yield 표현식은 함수 본문 내에서 다양한 내부 상태를 정의하는 데 사용됩니다(yield는 영어로 "출력"을 의미함). 🎜
    function fn() {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve(1000)
            }, 1000);
        })
    }
    function fn1() { return 'nanjiu' }
    async function fn2() {
        // const value = await fn() // await 右侧表达式为Promise,得到的结果就是Promise成功的value
        // const value = await '南玖'
        const value = await fn1()
        console.log('value', value)
    }
    fn2() // value 'nanjiu'
    🎜실행🎜
    console.log('script start')
    async function async1() {
        await async2()
        console.log('async1 end')
    }
    async function async2() {
        console.log('async2 end')
    }
    async1()
    
    setTimeout(function() {
        console.log('setTimeout')
    }, 0)
    
    new Promise(resolve => {
        console.log('Promise')
        resolve()
    })
    .then(function() {
        console.log('promise1')
    })
    .then(function() {
        console.log('promise2')
    })
    console.log('script end')
    🎜Generator 함수를 호출한 후 함수가 실행되지 않으며 반환되는 것은 함수 연산 결과 내부 상태를 가리키는 포인터 객체, 즉 Iterator 객체입니다. 다음으로, 반복자 객체의 next 메서드를 호출하여 포인터가 다음 상태로 이동하도록 해야 합니다. 🎜🎜위의 콜백 함수는 다음과 같이 작성할 수 있습니다. 🎜rrreee🎜Generator 함수에서 반환된 순회 개체는 next 메서드를 호출해야만 다음 내부 상태를 순회할 수 있으므로 실제로 다음과 같은 기능을 제공합니다. 실행을 일시 중지하는 함수 방법. yield 표현식은 일시 중지 플래그입니다. 🎜🎜traverser 객체의 next 메소드의 연산 로직은 다음과 같습니다. 🎜🎜(1) yield 표현식이 발생하면 후속 작업의 실행이 일시 중지되고 yield 바로 다음 표현식의 값이 반환된 객체로 사용됩니다. value 속성의 값입니다. 🎜🎜(2) 다음에 next 메서드가 호출되면 다음 yield 표현식이 나타날 때까지 실행이 계속됩니다. 🎜

    (3)如果没有再遇到新的yield表达式,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值。

    (4)如果该函数没有return语句,则返回的对象的value属性值为undefined

    yield表达式本身没有返回值,或者说总是返回undefinednext方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值。

    怎么理解这句话?我们来看下面这个例子:

    function* foo(x) {
      var y = 2 * (yield (x + 1));
      var z = yield (y / 3);
      return (x + y + z);
    }
    
    var a = foo(5);
    a.next() // Object{value:6, done:false}
    a.next() // Object{value:NaN, done:false}
    a.next() // Object{value:NaN, done:true}
    
    var b = foo(5);
    b.next() // { value:6, done:false }
    b.next(12) // { value:8, done:false }
    b.next(13) // { value:42, done:true }

    由于yield没有返回值,所以(yield(x+1))执行后的值是undefined,所以在第二次执行a.next()是其实是执行的2*undefined,所以值是NaN,所以下面b的例子中,第二次执行b.next()时传入了12,它会当成第一次b.next()的执行返回值,所以b的例子中能够正确计算。这里不能把next执行结果中的value值与yield返回值搞混了,它两不是一个东西

    yield与return的区别

    相同点:

    • 都能返回语句后面的那个表达式的值
    • 都可以暂停函数执行

    区别:

    • 一个函数可以有多个 yield,但是只能有一个 return
    • yield 有位置记忆功能,return 没有

    4.Async/await

    Async/await其实就是上面Generator的语法糖,async函数其实就相当于funciton *的作用,而await就相当与yield的作用。而在async/await机制中,自动包含了我们上述封装出来的spawn自动执行函数。

    所以上面的回调函数又可以写的更加简洁了:

    async function fetch() {
      	await ajax('aaa')
        await ajax('bbb')
        await ajax('ccc')
    }
    // 但这是在这三个请求有相互依赖的前提下可以这么写,不然会产生性能问题,因为你每一个请求都需要等待上一次请求完成后再发起请求,如果没有相互依赖的情况下,建议让它们同时发起请求,这里可以使用Promise.all()来处理

    async函数对Generator函数的改进,体现在以下四点:

    • 内置执行器:async函数执行与普通函数一样,不像Generator函数,需要调用next方法,或使用co模块才能真正执行
    • 语意化更清晰:asyncawait,比起星号和yield,语义更清楚了。async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。
    • 适用性更广:co模块约定,yield命令后面只能是 Thunk 函数或 Promise 对象,而async函数的await命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时会自动转成立即 resolved 的 Promise 对象)。
    • 返回值是Promise:async函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。你可以用then方法指定下一步的操作。

    async函数

    async函数的返回值为Promise对象,所以它可以调用then方法

    async function fn() {
      return 'async'
    }
    fn().then(res => {
      console.log(res) // 'async'
    })

    await表达式

    await 右侧的表达式一般为 promise 对象, 但也可以是其它的值

    • 如果表达式是 promise 对象, await 返回的是 promise 成功的值

    • 如果表达式是其它值, 直接将此值作为 await 的返回值

    • await后面是Promise对象会阻塞后面的代码,Promise 对象 resolve,然后得到 resolve 的值,作为 await 表达式的运算结果

    • 所以这就是await必须用在async的原因,async刚好返回一个Promise对象,可以异步执行阻塞

    function fn() {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve(1000)
            }, 1000);
        })
    }
    function fn1() { return 'nanjiu' }
    async function fn2() {
        // const value = await fn() // await 右侧表达式为Promise,得到的结果就是Promise成功的value
        // const value = await '南玖'
        const value = await fn1()
        console.log('value', value)
    }
    fn2() // value 'nanjiu'

    异步方案比较

    后三种方案都是为解决传统的回调函数而提出的,所以它们相对于回调函数的优势不言而喻。而async/await又是Generator函数的语法糖。

    • Promise的内部错误使用try catch捕获不到,只能只用then的第二个回调或catch来捕获,而async/await的错误可以用try catch捕获
    • Promise一旦新建就会立即执行,不会阻塞后面的代码,而async函数中await后面是Promise对象会阻塞后面的代码。
    • async函数会隐式地返回一个promise,该promisereosolve值就是函数return的值。
    • 使用async函数可以让代码更加简洁,不需要像Promise一样需要调用then方法来获取返回值,不需要写匿名函数处理Promise的resolve值,也不需要定义多余的data变量,还避免了嵌套代码。

    说了这么多,顺便看个题吧~

    console.log('script start')
    async function async1() {
        await async2()
        console.log('async1 end')
    }
    async function async2() {
        console.log('async2 end')
    }
    async1()
    
    setTimeout(function() {
        console.log('setTimeout')
    }, 0)
    
    new Promise(resolve => {
        console.log('Promise')
        resolve()
    })
    .then(function() {
        console.log('promise1')
    })
    .then(function() {
        console.log('promise2')
    })
    console.log('script end')

    解析:

    打印顺序应该是: script start -> async2 end -> Promise -> script end -> async1 end -> promise1 -> promise2 -> setTimeout

    老规矩,全局代码自上而下执行,先打印出script start,然后执行async1(),里面先遇到await async2(),执行async2,打印出async2 end,然后await后面的代码放入微任务队列,接着往下执行new Promise,打印出Promise,遇见了resolve,将第一个then方法放入微任务队列,接着往下执行打印出script end,全局代码执行完了,然后从微任务队列中取出第一个微任务执行,打印出async1 end,再取出第二个微任务执行,打印出promise1,然后这个then方法执行完了,当前Promise的状态为fulfilled,它也可以出发then的回调,所以第二个then这时候又被加进了微任务队列,然后再出微任务队列中取出这个微任务执行,打印出promise2,此时微任务队列为空,接着执行宏任务队列,打印出setTimeout

    解题技巧:

    • 无论是then还是catch里的回调内容只要代码正常执行或者正常返回,则当前新的Promise实例为fulfilled状态。如果有报错或返回Promise.reject()则新的Promise实例为rejected状态。
    • fulfilled状态能够触发then回调
    • rejected状态能够触发catch回调
    • 执行async函数,返回的是Promise对象
    • await相当于Promise的then并且同一作用域下await下面的内容全部作为then中回调的内容
    • 异步中先执行微任务,再执行宏任务

    【相关推荐:javascript学习教程

위 내용은 Promise, Generator 및 Async의 차이점에 대한 간략한 분석의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 juejin.cn에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제