Home > Web Front-end > JS Tutorial > body text

Let's talk about JavaScript asynchronous programming again_javascript skills

WBOY
Release: 2016-05-16 15:17:35
Original
1165 people have browsed it

With the development of front-end, the word asynchronous is becoming more and more common. Suppose we now have such an asynchronous task:

Initiate several requests to the server, and the results of each request are used as parameters for the next request.
Let’s take a look at what we have to do:

Callbacks

The first thing that comes to mind and the most commonly used is the callback function. Let’s make a simple encapsulation:

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

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

Copy after login

Hmm, looks pretty good! But when we try to nest multiple tasks, the code looks like this:

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) => {
      // ...
    })
  })
})

Copy after login

Oh my God! Let that pile }) go to hell!

So, we want to try to use the JavaScript event model:

1. Pub/Sub

In the processing of DOM events, Pub/Sub is a very common mechanism. For example, we need to add event monitoring to elements:

elem.addEventListener(type, (evt) => {
  // handler
})
Copy after login

So can we construct a similar model to handle asynchronous tasks?

The first thing is to build a distribution center and add the on / emit method:

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

Copy after login

Then we can use it like this:

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

Copy after login

There seems to be no revolutionary change compared to the callback function, but the advantage of this is: we can put the request and processing functions in different modules to reduce coupling.

2. Promise

The real revolutionary change is the Promise specification. With Promise, we can complete asynchronous tasks like this:

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

Copy after login

Great! It is written like a synchronous function!

Don’t worry, young man. We have even better:

3. Generators

Another big killer of ES6 is Generators[2]. In a generator function, we can interrupt the execution of the function through the yield statement, and iterate statements through the next method outside the function. More importantly, we can inject data into the function through the next method to dynamically change the behavior of the function. For example:

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}

Copy after login

Encapsulate our previous makeAjaxCall function through generator:

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

Copy after login

Oh! The logic seems very clear, but it feels so uncomfortable to have to inject iterator from the outside every time...

Don’t worry, let’s mix Promise and Generator and see what black magic will be produced:

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)

Copy after login

The runGen function looks like an automaton, so awesome!

Actually, this runGen method is an implementation of the ECMAScript 7 async function:

4. async function

In ES7, a more natural feature async function[3] is introduced. Using async function we can complete the task like this:

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}`)
})()

Copy after login

Just like when we combined Promise and Generator above, the await keyword also accepts a Promise. In async function, the remaining statements will be executed only after the statement after await is completed. The whole process is just like we use the runGen function to encapsulate the Generator.

The above are several JavaScript asynchronous programming modes summarized in this article. I hope it will be helpful to everyone's learning.

source:php.cn
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template