ホームページ  >  記事  >  ウェブフロントエンド  >  JavaScript 非同期プログラミングについてもう一度話しましょう_JavaScript スキル

JavaScript 非同期プログラミングについてもう一度話しましょう_JavaScript スキル

WBOY
WBOYオリジナル
2016-05-16 15:17:351156ブラウズ

フロントエンドの発展に伴い、非同期という言葉がますます一般的になってきました。次のような非同期タスクがあるとします。

サーバーに対していくつかのリクエストを開始し、各リクエストの結果が次のリクエストのパラメータとして使用されます。
何をしなければならないかを見てみましょう:

コールバック

最初に思い浮かび、最も一般的に使用されるのは、単純なカプセル化を作成してみましょう。

let makeAjaxCall = (url, cb) => {
  // do some ajax
  // callback with result
}

makeAjaxCall('http://url1', (result) => {
  result = JSON.parse(result)
})

うーん、なかなかいい感じですね!しかし、複数のタスクをネストしようとすると、コードは次のようになります:

makeAjaxCall('http://url1', (result) => {
  result = JSON.parse(result)

  makeAjaxCall(`http://url2?q=${result.query}`, (result) => {
    result = JSON.parse(result)

    makeAjaxCall(`http://url3?q=${result.query}`, (result) => {
      // ...
    })
  })
})

なんと!その山を地獄に落ちさせましょう })

そこで、JavaScript イベント モデル を使用してみます。

1. パブリッシュ/サブスクライブ

DOM イベントの処理では、Pub/Sub は非常に一般的なメカニズムです。たとえば、要素にイベント監視を追加する必要があります。

elem.addEventListener(type, (evt) => {
  // handler
})
では、非同期タスクを処理するために同様のモデルを構築できるでしょうか?

最初に、配送センターを構築し、on / Emit メソッドを追加します。

let PubSub = {
  events: {},
  on(type, handler) {
    let events = this.events
    events[type] = events[type] || []
    events[type].push(handler)
  },
  emit(type, ...datas) {
    let events = this.events

    if (!events[type]) {
      return
    }

    events[type].forEach((handler) => handler(...datas))
  }
}

その後、次のように使用できます:

const urls = [
  'http://url1',
  'http://url2',
  'http://url3'
]

let makeAjaxCall = (url) => {
  // do some ajax
  PubSub.emit('ajaxEnd', result)
}

let subscribe = (urls) => {
  let index = 0

  PubSub.on('ajaxEnd', (result) => {
    result = JSON.parse(result)

    if (urls[++index]) {
      makeAjaxCall(`${urls[index]}?q=${result.query}`)
    }
  })

  makeAjaxCall(urls[0])
}

コールバック関数と比べて革新的な変更はないようですが、この利点は、リクエスト関数と処理関数を別のモジュールに配置して結合を減らすことができることです。

2. 約束

本当の革命的な変更は Promise 仕様です。 Promise を使用すると、次のような非同期タスクを完了できます:

let makeAjaxCall = (url) => {
  return new Promise((resolve, reject) => {
    // do some ajax
    resolve(result)
  })
}

makeAjaxCall('http://url1')
  .then(JSON.parse)
  .then((result) => makeAjaxCall(`http://url2?q=${result.query}`))
  .then(JSON.parse)
  .then((result) => makeAjaxCall(`http://url3?q=${result.query}`))

すごいですね!同期関数のように書かれています。

心配しないでください、若者。さらに優れたものがあります:

3. ジェネレーター

ES6 のもう 1 つの大きなキラーはジェネレーター[2]です。ジェネレーター関数では、yield ステートメントを通じて関数の実行を中断し、関数の外部の next メソッドを通じてステートメントを反復できます。さらに重要なのは、next メソッドを通じて関数にデータを注入して、関数の動作を動的に変更できることです。関数。例:

function* gen() {
  let a = yield 1
  let b = yield a * 2
  return b
}

let it = gen()

it.next() // output: {value: 1, done: false}
it.next(10) // a = 10, output: {value: 20, done: false}
it.next(100) // b = 100, output: {value: 100, done: true}

ジェネレーターを通じて以前の makeAjaxCall 関数をカプセル化します:

let makeAjaxCall = (url) => {
  // do some ajax
  iterator.next(result)
}

function* requests() {
  let result = yield makeAjaxCall('http://url1')
  result = JSON.parse(result)
  result = yield makeAjaxCall(`http://url2?q=${result.query}`)
  result = JSON.parse(result)
  result = yield makeAjaxCall(`http://url3?q=${result.query}`)
}

let iterator = requests()
iterator.next() // get everything start

ああ!ロジックは非常に明確に見えますが、毎回外部からイテレータを挿入しなければならないのは非常に不快です...

心配しないで、Promise と Generator を混ぜて、どのような黒魔術が生成されるかを見てみましょう:

let makeAjaxCall = (url) => {
  return new Promise((resolve, reject) => {
    // do some ajax
    resolve(result)
  })
}

let runGen = (gen) => { 
  let it = gen()

  let continuer = (value, err) => {
    let ret

    try {
      ret = err ? it.throw(err) : it.next(value)
    } catch (e) {
      return Promise.reject(e)
    }

    if (ret.done) {
      return ret.value
    }

    return Promise
      .resolve(ret.value)
      .then(continuer)
      .catch((e) => continuer(null, e))
  }

  return continuer()
}

function* requests() {
  let result = yield makeAjaxCall('http://url1')
  result = JSON.parse(result)
  result = yield makeAjaxCall(`http://url2?q=${result.query}`)
  result = JSON.parse(result)
  result = yield makeAjaxCall(`http://url3?q=${result.query}`)
}

runGen(requests)

runGen 関数はオートマトンのように見えて、とても素晴らしいです!

実際、この runGen メソッドは ECMAScript 7 非同期関数の実装です:

4. 非同期関数

ES7 では、より自然な機能である async 関数[3]が導入されています。 async 関数を使用すると、次のようにタスクを完了できます:

let makeAjaxCall = (url) => {
  return new Promise((resolve, reject) => {
    // do some ajax
    resolve(result)
  })
}

;(async () => {
  let result = await makeAjaxCall('http://url1')
  result = JSON.parse(result)
  result = await makeAjaxCall(`http://url2?q=${result.query}`)
  result = JSON.parse(result)
  result = await makeAjaxCall(`http://url3?q=${result.query}`)
})()

上記の Promise と Generator を組み合わせたときと同様に、await キーワードも Promise を受け入れます。 async 関数では、残りのステートメントは await が完了した後でのみ実行されます。プロセス全体は、runGen 関数を使用してジェネレーターをカプセル化するのと同じです。

上記は、この記事にまとめられたいくつかの JavaScript 非同期プログラミング モードです。皆さんの学習に役立つことを願っています。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。