Heim  >  Artikel  >  Web-Frontend  >  Welche Methoden der asynchronen Programmierung in JavaScript gibt es? Einführung in asynchrone JavaScript-Programmiermethoden

Welche Methoden der asynchronen Programmierung in JavaScript gibt es? Einführung in asynchrone JavaScript-Programmiermethoden

不言
不言nach vorne
2019-01-14 09:35:092568Durchsuche

Was sind die Methoden der asynchronen Programmierung in JavaScript? Die Einführung asynchroner Programmiermethoden in JavaScript hat einen gewissen Referenzwert. Freunde in Not können sich darauf beziehen.

Wir wissen, dass die Ausführungsumgebung der Javascript-Sprache „Single-Threaded“ ist. Das bedeutet, dass jeweils nur eine Aufgabe erledigt werden kann. Wenn mehrere Aufgaben vorhanden sind, müssen diese in die Warteschlange gestellt werden, die vorherige Aufgabe wird abgeschlossen und dann wird die nächste Aufgabe ausgeführt.

Obwohl dieser Modus relativ einfach zu implementieren ist und die Ausführungsumgebung relativ einfach ist, müssen nachfolgende Aufgaben in die Warteschlange gestellt werden, solange eine Aufgabe lange dauert, was die Ausführung des gesamten Programms verzögert. Die häufige Nichtreaktion des Browsers (suspended dead) wird häufig dadurch verursacht, dass ein bestimmter Teil des Javascript-Codes über einen längeren Zeitraum ausgeführt wird (z. B. eine Endlosschleife), wodurch die gesamte Seite an dieser Stelle hängen bleibt und andere Aufgaben nicht ausgeführt werden können.

Um dieses Problem zu lösen, unterteilt die Javascript-Sprache den Aufgabenausführungsmodus in zwei Typen: synchron und asynchron. In diesem Artikel werden hauptsächlich verschiedene Methoden der asynchronen Programmierung vorgestellt. Durch Vergleich erhalten wir die beste Lösung für die asynchrone Programmierung!

1. Synchronisation und Asynchronität

Wir können im Allgemeinen verstehen, dass asynchron bedeutet, dass eine Aufgabe in zwei Teile geteilt wird, der erste Teil zuerst ausgeführt wird und dann andere Aufgaben ausgeführt werden usw. Wenn Sie bereit sind, gehen Sie zurück und führen Sie den zweiten Absatz aus. Der hinter der asynchronen Aufgabe rangierte Code wird sofort ausgeführt, ohne auf das Ende der asynchronen Aufgabe zu warten. Mit anderen Worten: Asynchrone Aufgaben haben keine „blockierende“ Wirkung. Beispielsweise gibt es eine Aufgabe, die Dateien zur Verarbeitung liest. Der asynchrone Ausführungsprozess ist wie folgt:

Welche Methoden der asynchronen Programmierung in JavaScript gibt es? Einführung in asynchrone JavaScript-Programmiermethoden

Diese Art der diskontinuierlichen Ausführung wird als asynchron bezeichnet . Dementsprechend wird die kontinuierliche Ausführung als Synchronisation bezeichnet

Welche Methoden der asynchronen Programmierung in JavaScript gibt es? Einführung in asynchrone JavaScript-Programmiermethoden

„Asynchroner Modus“ ist sehr wichtig. Auf der Browserseite sollten lang laufende Vorgänge asynchron ausgeführt werden, um zu verhindern, dass der Browser nicht mehr reagiert. Das beste Beispiel sind Ajax-Vorgänge. Auf der Serverseite ist der „asynchrone Modus“ sogar der einzige Modus, da die Ausführungsumgebung Single-Threaded ist und wenn alle HTTP-Anfragen synchron ausgeführt werden dürfen, sinkt die Leistung des Servers drastisch und er reagiert sehr schnell nicht mehr. Als nächstes stellen wir sechs Methoden der asynchronen Programmierung vor.

2. Rückruffunktion (Callback)

Die Rückruffunktion ist die grundlegendste Methode des asynchronen Betriebs. Der folgende Code ist ein Beispiel für eine Rückruffunktion:

ajax(url, () => {
    // 处理逻辑
})

Aber die Rückruffunktion hat eine fatale Schwäche, das heißt, es ist einfach, Callback hell (Callback hell) zu schreiben. Unter der Annahme, dass mehrere Anfragen Abhängigkeiten haben, können Sie den folgenden Code schreiben:

ajax(url, () => {
    // 处理逻辑
    ajax(url1, () => {
        // 处理逻辑
        ajax(url2, () => {
            // 处理逻辑
        })
    })
})

Der Vorteil der Rückruffunktion besteht darin, dass sie einfach, leicht zu verstehen und zu implementieren ist, der Nachteil besteht jedoch darin, dass sie dem nicht förderlich ist Durch das Lesen und Warten des Codes ist die Programmstruktur unübersichtlich und der Prozess schwer zu verfolgen (insbesondere, wenn mehrere Rückruffunktionen verschachtelt sind), und für jede Aufgabe kann nur eine Rückruffunktion angegeben werden. Darüber hinaus kann Try Catch nicht zum Abfangen von Fehlern verwendet werden und es kann nicht direkt zurückgegeben werden.

3. Ereignisüberwachung

Auf diese Weise hängt die Ausführung asynchroner Aufgaben nicht von der Reihenfolge des Codes ab, sondern davon, ob ein Ereignis eintritt .

Das Folgende sind zwei Funktionen f1 und f2. Die Programmierabsicht besteht darin, dass f2 warten muss, bis f1 abgeschlossen ist, bevor es ausgeführt werden kann. Binden Sie zunächst ein Ereignis an f1 (die hier verwendete jQuery-Schreibmethode).

f1.on('done', f2);

Die obige Codezeile bedeutet, dass f2 ausgeführt wird, wenn das erledigte Ereignis in f1 auftritt. Schreiben Sie dann f1 neu:

function f1() {
  setTimeout(function () {
    // ...
    f1.trigger('done');
  }, 1000);
}

Im obigen Code bedeutet f1.trigger('done'), dass nach Abschluss der Ausführung das Done-Ereignis sofort ausgelöst wird und somit mit der Ausführung von f2 begonnen wird.

Der Vorteil dieser Methode besteht darin, dass sie relativ einfach zu verstehen ist, mehrere Ereignisse binden kann, jedes Ereignis mehrere Rückruffunktionen angeben kann und „entkoppelt“ werden kann, was der Modularisierung förderlich ist. Der Nachteil besteht darin, dass das gesamte Programm ereignisgesteuert werden muss und der laufende Prozess sehr unklar wird. Beim Lesen des Codes ist es schwierig, den Hauptfluss zu erkennen.

4. Veröffentlichen und abonnieren

Wir gehen davon aus, dass es eine „Signalzentrale“ gibt. Wenn eine Aufgabe abgeschlossen ist, wird ein Signal an die Signalzentrale „veröffentlicht“. . Andere Aufgaben können dieses Signal von der Signalzentrale „abonnieren“, um zu erfahren, wann sie mit der Ausführung beginnen können. Dies wird als „Publish-Subscribe-Muster“ (Publish-Subscribe-Muster) oder auch als „Observer-Muster“ (Observer-Muster) bezeichnet.

Zuerst abonniert f2 das fertige Signal vom Signalzentrum jQuery.

jQuery.subscribe('done', f2);

Dann wird f1 wie folgt umgeschrieben:

function f1() {
  setTimeout(function () {
    // ...
    jQuery.publish('done');
  }, 1000);
}

Im obigen Code bedeutet jQuery.publish('done'), dass nach Abschluss der Ausführung von f1 das Fertigsignal freigegeben wird an die Signalzentrale jQuery Dies löst die Ausführung von f2 aus.
Nachdem f2 die Ausführung abgeschlossen hat, können Sie sich abmelden (unsubscribe)

jQuery.unsubscribe('done', f2);

这种方法的性质与“事件监听”类似,但是明显优于后者。因为可以通过查看“消息中心”,了解存在多少信号、每个信号有多少订阅者,从而监控程序的运行。

五、Promise/A+

Promise本意是承诺,在程序中的意思就是承诺我过一段时间后会给你一个结果。 什么时候会用到过一段时间?答案是异步操作,异步是指可能比较长时间才有结果的才做,例如网络请求、读取本地文件等

1.Promise的三种状态

  • Pending----Promise对象实例创建时候的初始状态

  • Fulfilled----可以理解为成功的状态

  • Rejected----可以理解为失败的状态

Welche Methoden der asynchronen Programmierung in JavaScript gibt es? Einführung in asynchrone JavaScript-Programmiermethoden

这个承诺一旦从等待状态变成为其他状态就永远不能更改状态了,比如说一旦状态变为 resolved 后,就不能再次改变为Fulfilled

let p = new Promise((resolve, reject) => {
  reject('reject')
  resolve('success')//无效代码不会执行
})
p.then(
  value => {
    console.log(value)
  },
  reason => {
    console.log(reason)//reject
  }
)

当我们在构造 Promise 的时候,构造函数内部的代码是立即执行的

new Promise((resolve, reject) => {
  console.log('new Promise')
  resolve('success')
})
console.log('end')
// new Promise => end

2.promise的链式调用

  • 每次调用返回的都是一个新的Promise实例(这就是then可用链式调用的原因)

  • 如果then中返回的是一个结果的话会把这个结果传递下一次then中的成功回调

  • 如果then中出现异常,会走下一个then的失败回调

  • 在 then中使用了return,那么 return 的值会被Promise.resolve() 包装(见例1,2)

  • then中可以不传递参数,如果不传递会透到下一个then中(见例3)

  • catch 会捕获到没有捕获的异常

接下来我们看几个例子:

  // 例1
  Promise.resolve(1)
  .then(res => {
    console.log(res)
    return 2 //包装成 Promise.resolve(2)
  })
  .catch(err => 3)
  .then(res => console.log(res))
// 例2
Promise.resolve(1)
  .then(x => x + 1)
  .then(x => {
    throw new Error('My Error')
  })
  .catch(() => 1)
  .then(x => x + 1)
  .then(x => console.log(x)) //2
  .catch(console.error)
// 例3
let fs = require('fs')
function read(url) {
  return new Promise((resolve, reject) => {
    fs.readFile(url, 'utf8', (err, data) => {
      if (err) reject(err)
      resolve(data)
    })
  })
}
read('./name.txt')
  .then(function(data) {
    throw new Error() //then中出现异常,会走下一个then的失败回调
  }) //由于下一个then没有失败回调,就会继续往下找,如果都没有,就会被catch捕获到
  .then(function(data) {
    console.log('data')
  })
  .then()
  .then(null, function(err) {
    console.log('then', err)// then error
  })
  .catch(function(err) {
    console.log('error')
  })

Promise不仅能够捕获错误,而且也很好地解决了回调地狱的问题,可以把之前的回调地狱例子改写为如下代码:

ajax(url)
  .then(res => {
      console.log(res)
      return ajax(url1)
  }).then(res => {
      console.log(res)
      return ajax(url2)
  }).then(res => console.log(res))

它也是存在一些缺点的,比如无法取消 Promise,错误需要通过回调函数捕获。

六、生成器Generators/ yield

Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同,Generator 最大的特点就是可以控制函数的执行。

  • 语法上,首先可以把它理解成,Generator 函数是一个状态机,封装了多个内部状态。

  • Generator 函数除了状态机,还是一个遍历器对象生成函数

  • 可暂停函数, yield可暂停,next方法可启动,每次返回的是yield后的表达式结果

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

我们先来看个例子:

function *foo(x) {
  let y = 2 * (yield (x + 1))
  let z = yield (y / 3)
  return (x + y + z)
}
let it = foo(5)
console.log(it.next())   // => {value: 6, done: false}
console.log(it.next(12)) // => {value: 8, done: false}
console.log(it.next(13)) // => {value: 42, done: true}

可能结果跟你想象不一致,接下来我们逐行代码分析:

  • 首先 Generator 函数调用和普通函数不同,它会返回一个迭代器

  • 当执行第一次 next 时,传参会被忽略,并且函数暂停在 yield (x + 1) 处,所以返回 5 + 1 = 6

  • 当执行第二次 next 时,传入的参数12就会被当作上一个yield表达式的返回值,如果你不传参,yield 永远返回 undefined。此时 let y = 2 12,所以第二个 yield 等于 2 12 / 3 = 8

  • 当执行第三次 next 时,传入的参数13就会被当作上一个yield表达式的返回值,所以 z = 13, x = 5, y = 24,相加等于 42

我们再来看个例子:有三个本地文件,分别1.txt,2.txt和3.txt,内容都只有一句话,下一个请求依赖上一个请求的结果,想通过Generator函数依次调用三个文件

//1.txt文件
2.txt
//2.txt文件
3.txt
//3.txt文件
结束
let fs = require('fs')
function read(file) {
  return new Promise(function(resolve, reject) {
    fs.readFile(file, 'utf8', function(err, data) {
      if (err) reject(err)
      resolve(data)
    })
  })
}
function* r() {
  let r1 = yield read('./1.txt')
  let r2 = yield read(r1)
  let r3 = yield read(r2)
  console.log(r1)
  console.log(r2)
  console.log(r3)
}
let it = r()
let { value, done } = it.next()
value.then(function(data) { // value是个promise
  console.log(data) //data=>2.txt
  let { value, done } = it.next(data)
  value.then(function(data) {
    console.log(data) //data=>3.txt
    let { value, done } = it.next(data)
    value.then(function(data) {
      console.log(data) //data=>结束
    })
  })
})
// 2.txt=>3.txt=>结束

从上例中我们看出手动迭代Generator 函数很麻烦,实现逻辑有点绕,而实际开发一般会配合 co 库去使用。co是一个为Node.js和浏览器打造的基于生成器的流程控制工具,借助于Promise,你可以使用更加优雅的方式编写非阻塞代码

安装co库只需:npm install co

上面例子只需两句话就可以轻松实现

function* r() {
  let r1 = yield read('./1.txt')
  let r2 = yield read(r1)
  let r3 = yield read(r2)
  console.log(r1)
  console.log(r2)
  console.log(r3)
}
let co = require('co')
co(r()).then(function(data) {
  console.log(data)
})
// 2.txt=>3.txt=>结束=>undefined

我们可以通过 Generator 函数解决回调地狱的问题,可以把之前的回调地狱例子改写为如下代码:

function *fetch() {
    yield ajax(url, () => {})
    yield ajax(url1, () => {})
    yield ajax(url2, () => {})
}
let it = fetch()
let result1 = it.next()
let result2 = it.next()
let result3 = it.next()

七、async/await

1.Async/Await简介

使用async/await,你可以轻松地达成之前使用生成器和co函数所做到的工作,它有如下特点:

  • async/await是基于Promise实现的,它不能用于普通的回调函数。

  • async/await与Promise一样,是非阻塞的。

  • async/await使得异步代码看起来像同步代码,这正是它的魔力所在。

一个函数如果加上 async ,那么该函数就会返回一个 Promise

async function async1() {
  return "1"
}
console.log(async1()) // -> Promise {<resolved>: "1"}</resolved>

Generator函数依次调用三个文件那个例子用async/await写法,只需几句话便可实现

let fs = require('fs')
function read(file) {
  return new Promise(function(resolve, reject) {
    fs.readFile(file, 'utf8', function(err, data) {
      if (err) reject(err)
      resolve(data)
    })
  })
}
async function readResult(params) {
  try {
    let p1 = await read(params, 'utf8')//await后面跟的是一个Promise实例
    let p2 = await read(p1, 'utf8')
    let p3 = await read(p2, 'utf8')
    console.log('p1', p1)
    console.log('p2', p2)
    console.log('p3', p3)
    return p3
  } catch (error) {
    console.log(error)
  }
}
readResult('1.txt').then( // async函数返回的也是个promise
  data => {
    console.log(data)
  },
  err => console.log(err)
)
// p1 2.txt
// p2 3.txt
// p3 结束
// 结束

2.Async/Await并发请求

如果请求两个文件,毫无关系,可以通过并发请求

let fs = require('fs')
function read(file) {
  return new Promise(function(resolve, reject) {
    fs.readFile(file, 'utf8', function(err, data) {
      if (err) reject(err)
      resolve(data)
    })
  })
}
function readAll() {
  read1()
  read2()//这个函数同步执行
}
async function read1() {
  let r = await read('1.txt','utf8')
  console.log(r)
}
async function read2() {
  let r = await read('2.txt','utf8')
  console.log(r)
}
readAll() // 2.txt 3.txt

八、总结

1.JS 异步编程进化史:callback -> promise -> generator -> async + await

2.async/await 函数的实现,就是将 Generator 函数和自动执行器,包装在一个函数里。

3.async/await可以说是异步终极解决方案了。

(1) async/await函数相对于Promise,优势体现在

  • 处理 then 的调用链,能够更清晰准确的写出代码

  • 并且也能优雅地解决回调地狱问题。

当然async/await函数也存在一些缺点,因为 await 将异步代码改造成了同步代码,如果多个异步代码没有依赖性却使用了 await 会导致性能上的降低,代码没有依赖性的话,完全可以使用 Promise.all 的方式。

(2) async/await函数对 Generator 函数的改进,体现在以下三点

  • 内置执行器。

Generator 函数的执行必须靠执行器,所以才有了 co 函数库,而 async 函数自带执行器。也就是说,async 函数的执行,与普通函数一模一样,只要一行

  • 更广的适用性。 co 函数库约定,yield 命令后面只能是 Thunk 函数或 Promise 对象,而 async 函数的 await 命令后面,可以跟 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)

  • 更好的语义。 async 和 await,比起星号和 yield,语义更清楚了。async 表示函数里有异步操作,await 表示紧跟在后面的表达式需要等待结果。

Das obige ist der detaillierte Inhalt vonWelche Methoden der asynchronen Programmierung in JavaScript gibt es? Einführung in asynchrone JavaScript-Programmiermethoden. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:segmentfault.com. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen