Heim > Web-Frontend > js-Tutorial > Hauptteil

Eine kurze Analyse der Unterschiede zwischen Promise, Generator und Async

青灯夜游
Freigeben: 2022-02-09 10:48:35
nach vorne
1823 Leute haben es durchsucht

Promise- und Async/await-Funktionen werden beide verwendet, um asynchrone Probleme in JavaScript zu lösen. Was ist also der Unterschied zwischen ihnen? Der folgende Artikel stellt Ihnen die Unterschiede zwischen Promise, Generator und Async vor. Ich hoffe, er wird Ihnen hilfreich sein!

Eine kurze Analyse der Unterschiede zwischen Promise, Generator und Async

Wir wissen, dass die Funktionen Promise und Async/await zur Lösung asynchroner Probleme in JavaScript verwendet werden, von der anfänglichen Rückruffunktion zur Behandlung von asynchronem Code >Promise kümmert sich um die asynchrone Verarbeitung, dann Generator um die asynchrone Verarbeitung und dann Async/await um die asynchrone Verarbeitung. Jedes technische Update sorgt dafür, dass JavaScript die asynchrone Verarbeitung effizienter handhabt. Elegant, aus heutiger Sicht gilt Async/await als die ultimative Lösung für die asynchrone Verarbeitung, wodurch die asynchrone Verarbeitung von JS immer mehr zu synchronen Aufgaben wird. Der höchste Stand der asynchronen Programmierung besteht darin, dass Sie sich keine Gedanken darüber machen müssen, ob sie asynchron ist oder nicht. PromiseAsync/await函数都是用来解决JavaScript中的异步问题的,从最开始的回调函数处理异步,到Promise处理异步,到Generator处理异步,再到Async/await处理异步,每一次的技术更新都使得JavaScript处理异步的方式更加优雅,从目前来看,Async/await被认为是异步处理的终极解决方案,让JS的异步处理越来越像同步任务。异步编程的最高境界,就是根本不用关心它是不是异步

异步解决方案的发展历程

1.回调函数

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

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

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

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

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

2.Promise

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

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

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

ajax('aaa').then(res=>{
  return ajax('bbb')
}).then(res=>{
  return ajax('ccc')
})
Nach dem Login kopieren

通过使用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) // 会打印
})
Nach dem Login kopieren
  • 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;
}
Nach dem Login kopieren

执行

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
Nach dem Login kopieren

调用 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表示执行结束
Nach dem Login kopieren

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

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

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

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

Die Entwicklungsgeschichte asynchroner Lösungen

1. Rückruffunktion🎜🎜Dem frühen Javascript-Code nach zu urteilen, wurde im Grunde die gesamte asynchrone Verarbeitung auf der Grundlage von Rückruffunktionen implementiert. Möglicherweise haben Sie den folgenden Code gesehen: 🎜
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 }
Nach dem Login kopieren
Nach dem Login kopieren
🎜Ja, er erschien zuvor in ES6 Code war überall zu sehen. Obwohl es das Problem der asynchronen Ausführung löst, folgt ihm das Problem der Callback-Hölle, das wir oft hören: 🎜
  • Es gibt überhaupt keine Reihenfolge: Die Ausführung verschachtelter Funktionen bringt Es ist schwierig zum Debuggen und ist der Wartung und dem Lesen nicht förderlich
  • Die Kopplung ist zu stark: Sobald eine bestimmte Verschachtelungsebene geändert wird, wirkt sich dies auf die Ausführung des gesamten Rückrufs aus
🎜< strong> Um dieses Problem zu lösen, schlug die Community daher zunächst Promise vor und implementierte es. ES6 schrieb es in den Sprachstandard, um seine Verwendung zu vereinheitlichen.
🎜🎜2.Promise🎜🎜Promise ist eine Lösung für die asynchrone Programmierung, die besser ist als herkömmliche Lösungen – Rückruffunktionen und -ereignisse – sinnvoller und leistungsfähiger. Es wurde entwickelt, um die durch Rückruffunktionen verursachten Probleme zu lösen. 🎜🎜Mit dem Promise-Objekt können asynchrone Vorgänge als synchroner Vorgangsprozess ausgedrückt werden, wodurch Schichten verschachtelter Rückruffunktionen vermieden werden. Darüber hinaus bietet das Promise-Objekt eine einheitliche Schnittstelle, die die Steuerung asynchroner Vorgänge erleichtert. 🎜🎜Also können wir die obige Rückruffunktion wie folgt ändern: (vorausgesetzt, Ajax wurde mit Promise umschlossen)🎜
async function fetch() {
  	await ajax(&#39;aaa&#39;)
    await ajax(&#39;bbb&#39;)
    await ajax(&#39;ccc&#39;)
}
// 但这是在这三个请求有相互依赖的前提下可以这么写,不然会产生性能问题,因为你每一个请求都需要等待上一次请求完成后再发起请求,如果没有相互依赖的情况下,建议让它们同时发起请求,这里可以使用Promise.all()来处理
Nach dem Login kopieren
Nach dem Login kopieren
🎜Durch die Verwendung von Promise zur asynchronen Verarbeitung sieht es eleganter aus als die vorherige Rückruffunktion ist klar und löst das Problem der Rückrufhölle. Der Kettenaufruf von then von Promise ist akzeptabler und entspricht unserer Vorstellung von Synchronisierung. 🎜🎜Aber Promise hat auch seine Mängel: 🎜
  • Die internen Fehler von Promise können nicht mit try Catch abgefangen werden, Sie können nur then verwenden s zweiter Rückruf oder catch zum Erfassen
async function fn() {
  return &#39;async&#39;
}
fn().then(res => {
  console.log(res) // &#39;async&#39;
})
Nach dem Login kopieren
Nach dem Login kopieren
  • Sobald ein Versprechen erstellt wurde, wird es sofort ausgeführt und kann nicht abgebrochen werden< /li>
  • ul>🎜Ich habe bereits einen Artikel geschrieben, in dem ich die Verwendung von Promise und seine internen Implementierungsprinzipien erklärt habe. Schüler, die Promise nicht gut verstehen, können einen Blick darauf werfen~🎜
    🎜Von der Verwendung bis zur Implementierung eines Promise🎜🎜https://juejin.cn/post/7051364317119119396🎜
    🎜 Die 3.Generator🎜🎜Generator-Funktion ist eine von ES6 bereitgestellte asynchrone Programmierlösung anders als herkömmliche Funktionen. Die Generator-Funktion bringt die asynchrone JavaScript-Programmierung auf eine ganz neue Ebene. 🎜🎜Deklaration🎜🎜 ähnelt der Funktionsdeklaration, außer dass das Schlüsselwort function und der Funktionsname vorhanden sind Dazwischen befindet sich ein Sternchen, und der yield-Ausdruck wird innerhalb des Funktionskörpers verwendet, um verschiedene interne Zustände zu definieren (yield bedeutet auf Englisch „Ausgabe“). 🎜
    function fn() {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve(1000)
            }, 1000);
        })
    }
    function fn1() { return &#39;nanjiu&#39; }
    async function fn2() {
        // const value = await fn() // await 右侧表达式为Promise,得到的结果就是Promise成功的value
        // const value = await &#39;南玖&#39;
        const value = await fn1()
        console.log(&#39;value&#39;, value)
    }
    fn2() // value &#39;nanjiu&#39;
    Nach dem Login kopieren
    Nach dem Login kopieren
    🎜Ausführung🎜
    console.log(&#39;script start&#39;)
    async function async1() {
        await async2()
        console.log(&#39;async1 end&#39;)
    }
    async function async2() {
        console.log(&#39;async2 end&#39;)
    }
    async1()
    
    setTimeout(function() {
        console.log(&#39;setTimeout&#39;)
    }, 0)
    
    new Promise(resolve => {
        console.log(&#39;Promise&#39;)
        resolve()
    })
    .then(function() {
        console.log(&#39;promise1&#39;)
    })
    .then(function() {
        console.log(&#39;promise2&#39;)
    })
    console.log(&#39;script end&#39;)
    Nach dem Login kopieren
    Nach dem Login kopieren
    🎜Nach dem Aufruf der Generatorfunktion wird die Funktion nicht ausgeführt, und was zurückgegeben wird, ist nicht die Das Ergebnis der Funktionsoperation ist ein Zeigerobjekt, das auf den internen Status zeigt, nämlich Iterator Object. Als nächstes muss die Methode next des Iteratorobjekts aufgerufen werden, wodurch der Zeiger in den nächsten Zustand wechselt. 🎜🎜Die obige Rückruffunktion kann also wie folgt geschrieben werden: 🎜rrreee🎜Da das von der Generatorfunktion zurückgegebene Traverser-Objekt den nächsten internen Status nur durch Aufrufen der next-Methode durchlaufen kann, stellt es tatsächlich einen bereit Weg zur Funktion, um die Ausführung anzuhalten. Der yield-Ausdruck ist das Pause-Flag. 🎜🎜Die Operationslogik der next-Methode des Traverser-Objekts ist wie folgt. 🎜🎜(1) Wenn ein yield-Ausdruck auftritt, wird die Ausführung nachfolgender Vorgänge angehalten und der Wert des Ausdrucks, der unmittelbar auf yield folgt, wird als zurückgegebenes Objekt verwendet Der Wert des Attributs value. 🎜🎜(2) Wenn die next-Methode das nächste Mal aufgerufen wird, wird die Ausführung fortgesetzt, bis der nächste yield-Ausdruck gefunden wird. 🎜

    (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 }
    Nach dem Login kopieren
    Nach dem Login kopieren

    由于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(&#39;aaa&#39;)
        await ajax(&#39;bbb&#39;)
        await ajax(&#39;ccc&#39;)
    }
    // 但这是在这三个请求有相互依赖的前提下可以这么写,不然会产生性能问题,因为你每一个请求都需要等待上一次请求完成后再发起请求,如果没有相互依赖的情况下,建议让它们同时发起请求,这里可以使用Promise.all()来处理
    Nach dem Login kopieren
    Nach dem Login kopieren

    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 &#39;async&#39;
    }
    fn().then(res => {
      console.log(res) // &#39;async&#39;
    })
    Nach dem Login kopieren
    Nach dem Login kopieren

    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 &#39;nanjiu&#39; }
    async function fn2() {
        // const value = await fn() // await 右侧表达式为Promise,得到的结果就是Promise成功的value
        // const value = await &#39;南玖&#39;
        const value = await fn1()
        console.log(&#39;value&#39;, value)
    }
    fn2() // value &#39;nanjiu&#39;
    Nach dem Login kopieren
    Nach dem Login kopieren

    异步方案比较

    后三种方案都是为解决传统的回调函数而提出的,所以它们相对于回调函数的优势不言而喻。而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(&#39;script start&#39;)
    async function async1() {
        await async2()
        console.log(&#39;async1 end&#39;)
    }
    async function async2() {
        console.log(&#39;async2 end&#39;)
    }
    async1()
    
    setTimeout(function() {
        console.log(&#39;setTimeout&#39;)
    }, 0)
    
    new Promise(resolve => {
        console.log(&#39;Promise&#39;)
        resolve()
    })
    .then(function() {
        console.log(&#39;promise1&#39;)
    })
    .then(function() {
        console.log(&#39;promise2&#39;)
    })
    console.log(&#39;script end&#39;)
    Nach dem Login kopieren
    Nach dem Login kopieren

    解析:

    打印顺序应该是: 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学习教程

    Das obige ist der detaillierte Inhalt vonEine kurze Analyse der Unterschiede zwischen Promise, Generator und Async. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Verwandte Etiketten:
Quelle:juejin.cn
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage
Über uns Haftungsausschluss Sitemap
Chinesische PHP-Website:Online-PHP-Schulung für das Gemeinwohl,Helfen Sie PHP-Lernenden, sich schnell weiterzuentwickeln!