ホームページ  >  記事  >  ウェブフロントエンド  >  Promise、Generator、Async の違いの簡単な分析

Promise、Generator、Async の違いの簡単な分析

青灯夜游
青灯夜游転載
2022-02-09 10:48:351851ブラウズ

Promise 関数と Async/await 関数は両方とも JavaScript の非同期問題を解決するために使用されますが、両者の違いは何ですか? Promise、Generator、Asyncの違いについては以下の記事で紹介していますので、お役に立てれば幸いです。

Promise、Generator、Async の違いの簡単な分析

Promise 関数と Async/await 関数は、JavaScript の非同期問題を解決するために使用されることを知っています。コールバック関数は非同期を処理し、Promise は非同期を処理し、Generator は非同期を処理し、次に Async/await は非同期を処理します。技術的な更新ごとに JavaScript が非同期を処理する方法になります。現在の観点から見ると、Async/await は非同期処理の究極のソリューションと考えられており、JS の非同期処理はますます同期タスクに似てきています。 非同期プログラミングの最高の状態は、それが非同期かどうかを心配する必要がないことです。

非同期ソリューションの開発の歴史

1. コールバック関数

ES6 の初期の Javascript コードより

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')
})

By using Promise を使用して非同期を処理します。以前のコールバック関数はより明確に見え、コールバック地獄の問題が解決されました。Promisethen チェーン コールはより受け入れられ、私たちの同期のアイデアと一致しています。

しかし、Promise には欠点もあります。

  • Promise の内部エラーは、try catch を使用して捕捉することはできません。使用できるのは try 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 をキャプチャするための ##then
    または
  • catch
  • の 2 番目のコールバックは、作成されるとすぐに実行され、キャンセルすることはできません

私は以前、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
ジェネレーター関数を呼び出した後、関数は実行されず、関数の演算結果が返されません。ポインタ 内部状態のポインタ オブジェクト、Iterator Object (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 の直後の式の値を返します。 オブジェクトの属性値。

(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 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はjuejin.cnで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。