Heim Web-Frontend js-Tutorial Die asynchrone Flusskontrolle von Node.js wird ausführlich erklärt

Die asynchrone Flusskontrolle von Node.js wird ausführlich erklärt

Jan 22, 2018 am 10:11 AM
javascript node.js 详解

Wenn Sie keine umfassende Erfahrung mit der Verwendung von Funktionsrückrufen haben, ist es immer noch etwas schwierig, diese Inhalte zu lesen. Aufgrund der einzigartigen asynchronen Eigenschaften von Node.js trat das Problem der „Rückrufhölle“ auf. In diesem Artikel habe ich ausführlicher beschrieben, wie das Problem des asynchronen Flusses gelöst werden kann. In diesem Artikel wird hauptsächlich eine kurze Diskussion der asynchronen Flusskontrolle in Node.js vorgestellt. Der Herausgeber findet sie recht gut, daher werde ich sie jetzt mit Ihnen teilen und als Referenz verwenden. Folgen wir dem Herausgeber und schauen wir uns das an. Ich hoffe, es kann allen helfen.

Der Artikel wird lang sein und dieser Artikel ist eine Erklärung des asynchronen Streaming-Modus. In diesem Artikel wird ein einfaches Web-Spider-Beispiel verwendet. Seine Funktion besteht darin, den Webseiteninhalt der angegebenen URL zu crawlen und im Projekt zu speichern. Am Ende des Artikels finden Sie die Quellcode-Demo des gesamten Artikels.

1. Nativer JavaScript-Modus

Dieser Artikel ist nicht für Anfänger, daher werden die meisten grundlegenden Inhalte weggelassen:

(spider_v1 .js )


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

const request = require("request");

const fs = require("fs");

const mkdirp = require("mkdirp");

const path = require("path");

const utilities = require("./utilities");

 

function spider(url, callback) {

  const filename = utilities.urlToFilename(url);

  console.log(`filename: ${filename}`);

 

  fs.exists(filename, exists => {

    if (!exists) {

      console.log(`Downloading ${url}`);

 

      request(url, (err, response, body) => {

        if (err) {

          callback(err);

        } else {

          mkdirp(path.dirname(filename), err => {

            if (err) {

              callback(err);

            } else {

              fs.writeFile(filename, body, err => {

                if (err) {

                  callback(err);

                } else {

                  callback(null, filename, true);

                }

              });

            }

          });

        }

      });

    } else {

      callback(null, filename, false);

    }

  });

}

 

spider(process.argv[2], (err, filename, downloaded) => {

  if (err) {

    console.log(err);

  } else if (downloaded) {

    console.log(`Completed the download of ${filename}`);

  } else {

    console.log(`${filename} was already downloaded`);

  }

});

Nach dem Login kopieren

Der Prozess des obigen Codes sieht ungefähr so ​​aus:

  1. URL in Dateinamen konvertieren

  2. Bestimmen Sie, ob der Dateiname existiert, kehren Sie direkt zurück, andernfalls fahren Sie mit dem nächsten Schritt fort

  3. Senden Sie eine Anfrage und holen Sie sich den Text

  4. Schreiben Sie den Text in die Datei

Dies ist eine sehr einfache Version des Spiders. Er kann nur den Inhalt einer URL crawlen Der Rückruf oben ist. Dann beginnen wir mit der Optimierung.

Zuallererst kann die if else-Methode optimiert werden. Hier ist natürlich ein Vergleichseffekt:


1

2

3

4

5

6

7

8

9

10

11

12

/// before

if (err) {

  callback(err);

} else {

  callback(null, filename, true);

}

 

/// after

if (err) {

  return callback(err);

}

callback(null, filename, true);

Nach dem Login kopieren

Der Code ist wie folgt geschrieben, es wird eine Verschachtelungsebene weniger geben, aber erfahrene Programmierer werden denken, dass das Schreiben auf diese Weise Fehler zu sehr hervorhebt. Unser Programmierfokus sollte auf der Verarbeitung korrekter Daten liegen, und es gibt auch eine solche Anforderung an die Lesbarkeit . .

Eine weitere Optimierung ist die Funktionsaufteilung. In der Spider-Funktion im obigen Code können die heruntergeladene Datei und die gespeicherte Datei aufgeteilt werden.

(spider_v2.js)


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

const request = require("request");

const fs = require("fs");

const mkdirp = require("mkdirp");

const path = require("path");

const utilities = require("./utilities");

 

function saveFile(filename, contents, callback) {

  mkdirp(path.dirname(filename), err => {

    if (err) {

      return callback(err);

    }

    fs.writeFile(filename, contents, callback);

  });

}

 

function download(url, filename, callback) {

  console.log(`Downloading ${url}`);

 

  request(url, (err, response, body) => {

    if (err) {

      return callback(err);

    }

    saveFile(filename, body, err => {

      if (err) {

        return callback(err);

      }

      console.log(`Downloaded and saved: ${url}`);

      callback(null, body);

    });

  })

}

 

function spider(url, callback) {

  const filename = utilities.urlToFilename(url);

  console.log(`filename: ${filename}`);

 

  fs.exists(filename, exists => {

    if (exists) {

      return callback(null, filename, false);

    }

    download(url, filename, err => {

      if (err) {

        return callback(err);

      }

      callback(null, filename, true);

    })

  });

}

 

spider(process.argv[2], (err, filename, downloaded) => {

  if (err) {

    console.log(err);

  } else if (downloaded) {

    console.log(`Completed the download of ${filename}`);

  } else {

    console.log(`${filename} was already downloaded`);

  }

});

Nach dem Login kopieren

Der obige Code ist im Grunde das Ergebnis einer nativen Optimierung, aber die Funktion dieser Spinne ist zu einfach. Wir müssen jetzt alle URLs auf einer bestimmten Webseite crawlen, was zu seriellen und parallelen Problemen führt.

(spider_v3.js)


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

const request = require("request");

const fs = require("fs");

const mkdirp = require("mkdirp");

const path = require("path");

const utilities = require("./utilities");

 

function saveFile(filename, contents, callback) {

  mkdirp(path.dirname(filename), err => {

    if (err) {

      return callback(err);

    }

    fs.writeFile(filename, contents, callback);

  });

}

 

function download(url, filename, callback) {

  console.log(`Downloading ${url}`);

 

  request(url, (err, response, body) => {

    if (err) {

      return callback(err);

    }

    saveFile(filename, body, err => {

      if (err) {

        return callback(err);

      }

      console.log(`Downloaded and saved: ${url}`);

      callback(null, body);

    });

  })

}

 

/// 最大的启发是实现了如何异步循环遍历数组

function spiderLinks(currentUrl, body, nesting, callback) {

  if (nesting === 0) {

    return process.nextTick(callback);

  }

 

  const links = utilities.getPageLinks(currentUrl, body);

 

  function iterate(index) {

    if (index === links.length) {

      return callback();

    }

    spider(links[index], nesting - 1, err => {

      if (err) {

        return callback(err);

      }

      iterate((index + 1));

    })

  }

 

  iterate(0);

}

 

function spider(url, nesting, callback) {

  const filename = utilities.urlToFilename(url);

 

  fs.readFile(filename, "utf8", (err, body) => {

    if (err) {

      if (err.code !== 'ENOENT') {

        return callback(err);

      }

      return download(url, filename, (err, body) => {

        if (err) {

          return callback(err);

        }

        spiderLinks(url, body, nesting, callback);

      });

    }

 

    spiderLinks(url, body, nesting, callback);

  });

}

 

spider(process.argv[2], 2, (err, filename, downloaded) => {

  if (err) {

    console.log(err);

  } else if (downloaded) {

    console.log(`Completed the download of ${filename}`);

  } else {

    console.log(`${filename} was already downloaded`);

  }

});

Nach dem Login kopieren

Der obige Code hat zwei weitere Kernfunktionen als der vorherige Code. Die erste besteht darin, ihn über zu erhalten Hilfsklasse Wenn es um Links in einem bestimmten Körper geht:


1

const links = utilities.getPageLinks(currentUrl, body);

Nach dem Login kopieren

Die interne Implementierung wird nicht erklärt:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

/// 最大的启发是实现了如何异步循环遍历数组

function spiderLinks(currentUrl, body, nesting, callback) {

  if (nesting === 0) {

    return process.nextTick(callback);

  }

 

  const links = utilities.getPageLinks(currentUrl, body);

 

  function iterate(index) {

    if (index === links.length) {

      return callback();

    }

    spider(links[index], nesting - 1, err => {

      if (err) {

        return callback(err);

      }

      iterate((index + 1));

    })

  }

 

  iterate(0);

}

Nach dem Login kopieren

Man kann sagen, dass der kleine Code oben ein natives Muster für die Implementierung der asynchronen Serialisierung ist. Darüber hinaus wird das Konzept der Verschachtelung eingeführt. Über dieses Attribut kann die Crawling-Ebene gesteuert werden.

Zu diesem Zeitpunkt haben wir die serielle Funktion vollständig implementiert. Unter Berücksichtigung der Leistung müssen wir die parallele Crawling-Funktion entwickeln.

(spider_v4.js)


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

const request = require("request");

const fs = require("fs");

const mkdirp = require("mkdirp");

const path = require("path");

const utilities = require("./utilities");

 

function saveFile(filename, contents, callback) {

  mkdirp(path.dirname(filename), err => {

    if (err) {

      return callback(err);

    }

    fs.writeFile(filename, contents, callback);

  });

}

 

function download(url, filename, callback) {

  console.log(`Downloading ${url}`);

 

  request(url, (err, response, body) => {

    if (err) {

      return callback(err);

    }

    saveFile(filename, body, err => {

      if (err) {

        return callback(err);

      }

      console.log(`Downloaded and saved: ${url}`);

      callback(null, body);

    });

  })

}

 

/// 最大的启发是实现了如何异步循环遍历数组

function spiderLinks(currentUrl, body, nesting, callback) {

  if (nesting === 0) {

    return process.nextTick(callback);

  }

 

  const links = utilities.getPageLinks(currentUrl, body);

  if (links.length === 0) {

    return process.nextTick(callback);

  }

 

  let completed = 0, hasErrors = false;

 

  function done(err) {

    if (err) {

      hasErrors = true;

      return callback(err);

    }

 

    if (++completed === links.length && !hasErrors) {

      return callback();

    }

  }

 

  links.forEach(link => {

    spider(link, nesting - 1, done);

  });

}

 

const spidering = new Map();

 

function spider(url, nesting, callback) {

  if (spidering.has(url)) {

    return process.nextTick(callback);

  }

 

  spidering.set(url, true);

 

  const filename = utilities.urlToFilename(url);

 

  /// In this pattern, there will be some issues.

  /// Possible problems to download the same url again and again。

  fs.readFile(filename, "utf8", (err, body) => {

    if (err) {

      if (err.code !== 'ENOENT') {

        return callback(err);

      }

      return download(url, filename, (err, body) => {

        if (err) {

          return callback(err);

        }

        spiderLinks(url, body, nesting, callback);

      });

    }

 

    spiderLinks(url, body, nesting, callback);

  });

}

 

spider(process.argv[2], 2, (err, filename, downloaded) => {

  if (err) {

    console.log(err);

  } else if (downloaded) {

    console.log(`Completed the download of ${filename}`);

  } else {

    console.log(`${filename} was already downloaded`);

  }

});

Nach dem Login kopieren

Dieser Code ist ebenfalls sehr einfach und hat zwei Kerninhalte. Eine Möglichkeit besteht darin, Parallelität zu implementieren:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

/// 最大的启发是实现了如何异步循环遍历数组

function spiderLinks(currentUrl, body, nesting, callback) {

  if (nesting === 0) {

    return process.nextTick(callback);

  }

 

  const links = utilities.getPageLinks(currentUrl, body);

  if (links.length === 0) {

    return process.nextTick(callback);

  }

 

  let completed = 0, hasErrors = false;

 

  function done(err) {

    if (err) {

      hasErrors = true;

      return callback(err);

    }

 

    if (++completed === links.length && !hasErrors) {

      return callback();

    }

  }

 

  links.forEach(link => {

    spider(link, nesting - 1, done);

  });

}

Nach dem Login kopieren

Der obige Code kann als Muster zum Erreichen von Parallelität bezeichnet werden. Dies wird durch Schleifendurchquerung erreicht. Ein weiterer Kern besteht darin, dass es bei der Verwendung von fs.exists Probleme geben wird und dieselbe Datei möglicherweise wiederholt heruntergeladen wird. Die Lösung hier ist:

  • Verwenden Sie Map To Eine bestimmte URL zwischenspeichern, die URL sollte als Schlüssel verwendet werden

Jetzt haben wir eine neue Anforderung, die maximale Anzahl gleichzeitiger Parallelitäten zu begrenzen, deshalb stellen wir hier eine vor, die meiner Meinung nach die ist Wichtigstes Konzept: Warteschlange.

(task-Queue.js)


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

class TaskQueue {

  constructor(concurrency) {

    this.concurrency = concurrency;

    this.running = 0;

    this.queue = [];

  }

 

  pushTask(task) {

    this.queue.push(task);

    this.next();

  }

 

  next() {

    while (this.running < this.concurrency && this.queue.length) {

      const task = this.queue.shift();

      task(() => {

        this.running--;

        this.next();

      });

      this.running++;

    }

  }

}

 

module.exports = TaskQueue;

Nach dem Login kopieren

Der obige Code ist der Implementierungscode der Warteschlange. Der Kern ist die next()-Methode Wie Sie sehen können, wird die Aufgabe sofort ausgeführt, wenn sie zur Warteschlange hinzugefügt wird. Dies bedeutet nicht, dass die Aufgabe sofort ausgeführt werden muss, sondern dass next sofort aufgerufen wird.

(spider_v5.js)


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

const request = require("request");

const fs = require("fs");

const mkdirp = require("mkdirp");

const path = require("path");

const utilities = require("./utilities");

const TaskQueue = require("./task-Queue");

const downloadQueue = new TaskQueue(2);

 

function saveFile(filename, contents, callback) {

  mkdirp(path.dirname(filename), err => {

    if (err) {

      return callback(err);

    }

    fs.writeFile(filename, contents, callback);

  });

}

 

function download(url, filename, callback) {

  console.log(`Downloading ${url}`);

 

  request(url, (err, response, body) => {

    if (err) {

      return callback(err);

    }

    saveFile(filename, body, err => {

      if (err) {

        return callback(err);

      }

      console.log(`Downloaded and saved: ${url}`);

      callback(null, body);

    });

  })

}

 

/// 最大的启发是实现了如何异步循环遍历数组

function spiderLinks(currentUrl, body, nesting, callback) {

  if (nesting === 0) {

    return process.nextTick(callback);

  }

 

  const links = utilities.getPageLinks(currentUrl, body);

  if (links.length === 0) {

    return process.nextTick(callback);

  }

 

  let completed = 0, hasErrors = false;

 

  links.forEach(link => {

    /// 给队列出传递一个任务,这个任务首先是一个函数,其次该函数接受一个参数

    /// 当调用任务时,触发该函数,然后给函数传递一个参数,告诉该函数在任务结束时干什么

    downloadQueue.pushTask(done => {

      spider(link, nesting - 1, err => {

        /// 这里表示,只要发生错误,队列就会退出

        if (err) {

          hasErrors = true;

          return callback(err);

        }

        if (++completed === links.length && !hasErrors) {

          callback();

        }

 

        done();

      });

    });

 

  });

}

 

const spidering = new Map();

 

function spider(url, nesting, callback) {

  if (spidering.has(url)) {

    return process.nextTick(callback);

  }

 

  spidering.set(url, true);

 

  const filename = utilities.urlToFilename(url);

 

  /// In this pattern, there will be some issues.

  /// Possible problems to download the same url again and again。

  fs.readFile(filename, "utf8", (err, body) => {

    if (err) {

      if (err.code !== &#39;ENOENT&#39;) {

        return callback(err);

      }

      return download(url, filename, (err, body) => {

        if (err) {

          return callback(err);

        }

        spiderLinks(url, body, nesting, callback);

      });

    }

 

    spiderLinks(url, body, nesting, callback);

  });

}

 

spider(process.argv[2], 2, (err, filename, downloaded) => {

  if (err) {

    console.log(`error: ${err}`);

  } else if (downloaded) {

    console.log(`Completed the download of ${filename}`);

  } else {

    console.log(`${filename} was already downloaded`);

  }

});

Nach dem Login kopieren

Um die Anzahl der Parallelitäten zu begrenzen, fügen Sie daher einfach die Aufgabendurchquerung in die Warteschlange der SpiderLinks-Methode ein wird den Zweck erfüllen. Das ist relativ einfach.

Bisher haben wir natives JavaScript verwendet, um einen Webspider mit relativ vollständigen Funktionen zu implementieren, der sowohl seriell als auch gleichzeitig sein kann und auch die Anzahl der Parallelitäten steuern kann.

2. Die Verwendung der asynchronen Bibliothek

Das Einfügen verschiedener Funktionen in verschiedene Funktionen bringt uns große Vorteile. Die asynchrone Bibliothek ist sehr beliebt und ihre Leistung ist auch gut. es basiert auf einem internen Rückruf.

(spider_v6.js)


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

const request = require("request");

const fs = require("fs");

const mkdirp = require("mkdirp");

const path = require("path");

const utilities = require("./utilities");

const series = require("async/series");

const eachSeries = require("async/eachSeries");

 

function download(url, filename, callback) {

  console.log(`Downloading ${url}`);

 

  let body;

 

  series([

    callback => {

      request(url, (err, response, resBody) => {

        if (err) {

          return callback(err);

        }

        body = resBody;

        callback();

      });

    },

    mkdirp.bind(null, path.dirname(filename)),

    callback => {

      fs.writeFile(filename, body, callback);

    }

  ], err => {

    if (err) {

      return callback(err);

    }

    console.log(`Downloaded and saved: ${url}`);

    callback(null, body);

  });

}

 

/// 最大的启发是实现了如何异步循环遍历数组

function spiderLinks(currentUrl, body, nesting, callback) {

  if (nesting === 0) {

    return process.nextTick(callback);

  }

 

  const links = utilities.getPageLinks(currentUrl, body);

  if (links.length === 0) {

    return process.nextTick(callback);

  }

 

  eachSeries(links, (link, cb) => {

    "use strict";

    spider(link, nesting - 1, cb);

  }, callback);

}

 

const spidering = new Map();

 

function spider(url, nesting, callback) {

  if (spidering.has(url)) {

    return process.nextTick(callback);

  }

 

  spidering.set(url, true);

 

  const filename = utilities.urlToFilename(url);

 

  fs.readFile(filename, "utf8", (err, body) => {

    if (err) {

      if (err.code !== &#39;ENOENT&#39;) {

        return callback(err);

      }

      return download(url, filename, (err, body) => {

        if (err) {

          return callback(err);

        }

        spiderLinks(url, body, nesting, callback);

      });

    }

 

    spiderLinks(url, body, nesting, callback);

  });

}

 

spider(process.argv[2], 1, (err, filename, downloaded) => {

  if (err) {

    console.log(err);

  } else if (downloaded) {

    console.log(`Completed the download of ${filename}`);

  } else {

    console.log(`${filename} was already downloaded`);

  }

});

Nach dem Login kopieren

Im obigen Code verwenden wir nur drei asynchrone Funktionen:


1

2

3

const series = require("async/series"); // 串行

const eachSeries = require("async/eachSeries"); // 并行

const queue = require("async/queue"); // 队列

Nach dem Login kopieren

Da es relativ einfach ist, werde ich es nicht erklären. Der Code für die asynchrone Warteschlange befindet sich in (spider_v7.js), der unserer oben angepassten Warteschlange sehr ähnlich ist, und ich werde ihn nicht weiter erklären.

3.Promise

Promise ist ein Protokoll, das dieses Protokoll implementiert. Einfach ausgedrückt ist ein Versprechen eine Vereinbarung. Wenn es abgeschlossen ist, wird seine Auflösungsmethode aufgerufen. Wenn es fehlschlägt, wird seine Ablehnungsmethode aufgerufen. Es implementiert die Methode then und gibt dann das Versprechen selbst zurück, wodurch eine Aufrufkette gebildet wird.

其实Promise的内容有很多,在实际应用中是如何把普通的函数promise化。这方面的内容在这里也不讲了,我自己也不够格

(spider_v8.js)


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

const utilities = require("./utilities");

const request = utilities.promisify(require("request"));

const fs = require("fs");

const readFile = utilities.promisify(fs.readFile);

const writeFile = utilities.promisify(fs.writeFile);

const mkdirp = utilities.promisify(require("mkdirp"));

const path = require("path");

 

 

function saveFile(filename, contents, callback) {

  mkdirp(path.dirname(filename), err => {

    if (err) {

      return callback(err);

    }

    fs.writeFile(filename, contents, callback);

  });

}

 

function download(url, filename) {

  console.log(`Downloading ${url}`);

 

  let body;

 

  return request(url)

    .then(response => {

      "use strict";

      body = response.body;

      return mkdirp(path.dirname(filename));

    })

    .then(() => writeFile(filename, body))

    .then(() => {

      "use strict";

      console.log(`Downloaded adn saved: ${url}`);

      return body;

    });

}

 

/// promise编程的本质就是为了解决在函数中设置回调函数的问题

/// 通过中间层promise来实现异步函数同步化

function spiderLinks(currentUrl, body, nesting) {

  let promise = Promise.resolve();

  if (nesting === 0) {

    return promise;

  }

 

  const links = utilities.getPageLinks(currentUrl, body);

 

  links.forEach(link => {

    "use strict";

    promise = promise.then(() => spider(link, nesting - 1));

  });

 

  return promise;

}

 

function spider(url, nesting) {

  const filename = utilities.urlToFilename(url);

 

  return readFile(filename, "utf8")

    .then(

      body => spiderLinks(url, body, nesting),

      err => {

        "use strict";

        if (err.code !== &#39;ENOENT&#39;) {

          /// 抛出错误,这个方便与在整个异步链的最后通过呢catch来捕获这个链中的错误

          throw err;

        }

        return download(url, filename)

          .then(body => spiderLinks(url, body, nesting));

      }

    );

}

 

spider(process.argv[2], 1)

  .then(() => {

    "use strict";

    console.log(&#39;Download complete&#39;);

  })

  .catch(err => {

    "use strict";

    console.log(err);

  });

Nach dem Login kopieren

可以看到上边的代码中的函数都是没有callback的,只需要在最后catch就可以了。

在设计api的时候,应该支持两种方式,及支持callback,又支持promise


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

function asyncpision(pidend, pisor, cb) {

  return new Promise((resolve, reject) => {

    "use strict";

    process.nextTick(() => {

      const result = pidend / pisor;

      if (isNaN(result) || !Number.isFinite(result)) {

        const error = new Error("Invalid operands");

        if (cb) {

          cb(error);

        }

        return reject(error);

      }

 

      if (cb) {

        cb(null, result);

      }

      resolve(result);

    });

  });

}

 

asyncpision(10, 2, (err, result) => {

  "use strict";

  if (err) {

    return console.log(err);

  }

  console.log(result);

});

 

asyncpision(22, 11)

  .then((result) => console.log(result))

  .catch((err) => console.log(err));

Nach dem Login kopieren

4.Generator

Generator很有意思,他可以让暂停函数和恢复函数,利用thunkify和co这两个库,我们下边的代码实现起来非常酷。

(spider_v9.js)


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

const thunkify = require("thunkify");

const co = require("co");

const path = require("path");

const utilities = require("./utilities");

 

const request = thunkify(require("request"));

const fs = require("fs");

const mkdirp = thunkify(require("mkdirp"));

const readFile = thunkify(fs.readFile);

const writeFile = thunkify(fs.writeFile);

const nextTick = thunkify(process.nextTick);

 

function* download(url, filename) {

  console.log(`Downloading ${url}`);

 

  const response = yield request(url);

  console.log(response);

 

  const body = response[1];

  yield mkdirp(path.dirname(filename));

 

  yield writeFile(filename, body);

 

  console.log(`Downloaded and saved ${url}`);

  return body;

}

 

function* spider(url, nesting) {

  const filename = utilities.urlToFilename(url);

 

  let body;

 

  try {

    body = yield readFile(filename, "utf8");

  } catch (err) {

    if (err.code !== &#39;ENOENT&#39;) {

      throw err;

    }

    body = yield download(url, filename);

  }

 

  yield spiderLinks(url, body, nesting);

}

 

function* spiderLinks(currentUrl, body, nesting) {

  if (nesting === 0) {

    return nextTick();

  }

 

  const links = utilities.getPageLinks(currentUrl, body);

 

  for (let i = 0; i < links.length; i++) {

    yield spider(links[i], nesting - 1);

  }

}

 

/// 通过co就自动处理了回调函数,直接返回了回调函数中的参数,把这些参数放到一个数组中,但是去掉了err信息

co(function* () {

  try {

    yield spider(process.argv[2], 1);

    console.log(&#39;Download complete&#39;);

  } catch (err) {

    console.log(err);

  }

});

Nach dem Login kopieren

总结

我并没有写promise和generator并发的代码。以上这些内容来自于这本书nodejs-design-patterns 。

相关推荐:

Node.js之关于异步流控制的简单介绍

微信小程序中Promise进行异步流程处理的实现过程

小程序开发之利用co处理异步流程的实例教程

Das obige ist der detaillierte Inhalt vonDie asynchrone Flusskontrolle von Node.js wird ausführlich erklärt. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

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

Heiße KI -Werkzeuge

Undresser.AI Undress

Undresser.AI Undress

KI-gestützte App zum Erstellen realistischer Aktfotos

AI Clothes Remover

AI Clothes Remover

Online-KI-Tool zum Entfernen von Kleidung aus Fotos.

Undress AI Tool

Undress AI Tool

Ausziehbilder kostenlos

Clothoff.io

Clothoff.io

KI-Kleiderentferner

Video Face Swap

Video Face Swap

Tauschen Sie Gesichter in jedem Video mühelos mit unserem völlig kostenlosen KI-Gesichtstausch-Tool aus!

Heiße Werkzeuge

Notepad++7.3.1

Notepad++7.3.1

Einfach zu bedienender und kostenloser Code-Editor

SublimeText3 chinesische Version

SublimeText3 chinesische Version

Chinesische Version, sehr einfach zu bedienen

Senden Sie Studio 13.0.1

Senden Sie Studio 13.0.1

Leistungsstarke integrierte PHP-Entwicklungsumgebung

Dreamweaver CS6

Dreamweaver CS6

Visuelle Webentwicklungstools

SublimeText3 Mac-Version

SublimeText3 Mac-Version

Codebearbeitungssoftware auf Gottesniveau (SublimeText3)

Ausführliche Erklärung zur Erlangung von Administratorrechten in Win11 Ausführliche Erklärung zur Erlangung von Administratorrechten in Win11 Mar 08, 2024 pm 03:06 PM

Das Windows-Betriebssystem ist eines der beliebtesten Betriebssysteme der Welt und seine neue Version Win11 hat viel Aufmerksamkeit erregt. Im Win11-System ist die Erlangung von Administratorrechten ein wichtiger Vorgang. Mit Administratorrechten können Benutzer weitere Vorgänge und Einstellungen auf dem System durchführen. In diesem Artikel wird ausführlich beschrieben, wie Sie Administratorrechte im Win11-System erhalten und wie Sie Berechtigungen effektiv verwalten. Im Win11-System werden Administratorrechte in zwei Typen unterteilt: lokaler Administrator und Domänenadministrator. Ein lokaler Administrator verfügt über vollständige Administratorrechte für den lokalen Computer

Detaillierte Erläuterung der Divisionsoperation in Oracle SQL Detaillierte Erläuterung der Divisionsoperation in Oracle SQL Mar 10, 2024 am 09:51 AM

Detaillierte Erläuterung der Divisionsoperation in OracleSQL In OracleSQL ist die Divisionsoperation eine häufige und wichtige mathematische Operation, die zur Berechnung des Ergebnisses der Division zweier Zahlen verwendet wird. Division wird häufig in Datenbankabfragen verwendet. Daher ist das Verständnis der Divisionsoperation und ihrer Verwendung in OracleSQL eine der wesentlichen Fähigkeiten für Datenbankentwickler. In diesem Artikel werden die relevanten Kenntnisse über Divisionsoperationen in OracleSQL ausführlich erörtert und spezifische Codebeispiele als Referenz für die Leser bereitgestellt. 1. Divisionsoperation in OracleSQL

Detaillierte Erläuterung der Rolle und Verwendung des PHP-Modulo-Operators Detaillierte Erläuterung der Rolle und Verwendung des PHP-Modulo-Operators Mar 19, 2024 pm 04:33 PM

Der Modulo-Operator (%) in PHP wird verwendet, um den Rest der Division zweier Zahlen zu ermitteln. In diesem Artikel werden wir die Rolle und Verwendung des Modulo-Operators im Detail besprechen und spezifische Codebeispiele bereitstellen, um den Lesern ein besseres Verständnis zu erleichtern. 1. Die Rolle des Modulo-Operators Wenn wir in der Mathematik eine ganze Zahl durch eine andere ganze Zahl dividieren, erhalten wir einen Quotienten und einen Rest. Wenn wir beispielsweise 10 durch 3 dividieren, ist der Quotient 3 und der Rest ist 1. Um diesen Rest zu ermitteln, wird der Modulo-Operator verwendet. 2. Verwendung des Modulo-Operators In PHP verwenden Sie das %-Symbol, um den Modul darzustellen

Detaillierte Erläuterung der Funktion system() des Linux-Systemaufrufs Detaillierte Erläuterung der Funktion system() des Linux-Systemaufrufs Feb 22, 2024 pm 08:21 PM

Detaillierte Erläuterung der Funktion system() des Linux-Systems Der Systemaufruf ist ein sehr wichtiger Teil des Linux-Betriebssystems. Er bietet eine Möglichkeit, mit dem Systemkernel zu interagieren. Unter diesen ist die Funktion system() eine der am häufigsten verwendeten Systemaufruffunktionen. In diesem Artikel wird die Verwendung der Funktion system() ausführlich vorgestellt und entsprechende Codebeispiele bereitgestellt. Grundlegende Konzepte von Systemaufrufen Systemaufrufe sind eine Möglichkeit für Benutzerprogramme, mit dem Betriebssystemkernel zu interagieren. Benutzerprogramme fordern das Betriebssystem an, indem sie Systemaufruffunktionen aufrufen

Einfaches JavaScript-Tutorial: So erhalten Sie den HTTP-Statuscode Einfaches JavaScript-Tutorial: So erhalten Sie den HTTP-Statuscode Jan 05, 2024 pm 06:08 PM

JavaScript-Tutorial: So erhalten Sie HTTP-Statuscode. Es sind spezifische Codebeispiele erforderlich. Vorwort: Bei der Webentwicklung ist häufig die Dateninteraktion mit dem Server erforderlich. Bei der Kommunikation mit dem Server müssen wir häufig den zurückgegebenen HTTP-Statuscode abrufen, um festzustellen, ob der Vorgang erfolgreich ist, und die entsprechende Verarbeitung basierend auf verschiedenen Statuscodes durchführen. In diesem Artikel erfahren Sie, wie Sie mit JavaScript HTTP-Statuscodes abrufen und einige praktische Codebeispiele bereitstellen. Verwenden von XMLHttpRequest

Detaillierte Erläuterung des Linux-Befehls „curl'. Detaillierte Erläuterung des Linux-Befehls „curl'. Feb 21, 2024 pm 10:33 PM

Detaillierte Erläuterung des Linux-Befehls „curl“ Zusammenfassung: Curl ist ein leistungsstarkes Befehlszeilentool für die Datenkommunikation mit dem Server. In diesem Artikel wird die grundlegende Verwendung des Curl-Befehls vorgestellt und tatsächliche Codebeispiele bereitgestellt, um den Lesern zu helfen, den Befehl besser zu verstehen und anzuwenden. 1. Was ist Locken? Curl ist ein Befehlszeilentool zum Senden und Empfangen verschiedener Netzwerkanfragen. Es unterstützt mehrere Protokolle wie HTTP, FTP, TELNET usw. und bietet umfangreiche Funktionen wie Datei-Upload, Datei-Download, Datenübertragung und Proxy

Erfahren Sie mehr über Promise.resolve() Erfahren Sie mehr über Promise.resolve() Feb 18, 2024 pm 07:13 PM

Eine detaillierte Erklärung von Promise.resolve() erfordert spezifische Codebeispiele. Promise ist ein Mechanismus in JavaScript zur Verarbeitung asynchroner Vorgänge. In der tatsächlichen Entwicklung ist es häufig erforderlich, einige asynchrone Aufgaben zu verarbeiten, die nacheinander ausgeführt werden müssen, und die Methode Promise.resolve () wird verwendet, um ein erfülltes Promise-Objekt zurückzugeben. Promise.resolve() ist eine statische Methode der Promise-Klasse, die a akzeptiert

Detaillierte Erläuterung der Numpy-Versionsabfragemethode Detaillierte Erläuterung der Numpy-Versionsabfragemethode Jan 19, 2024 am 08:20 AM

Numpy ist eine Python-Bibliothek für wissenschaftliches Rechnen, die eine Fülle von Array-Operationsfunktionen und -Tools bietet. Wenn Sie die Numpy-Version aktualisieren, müssen Sie die aktuelle Version abfragen, um die Kompatibilität sicherzustellen. In diesem Artikel wird die Methode der Numpy-Versionsabfrage ausführlich vorgestellt und spezifische Codebeispiele bereitgestellt. Methode 1: Verwenden Sie Python-Code, um die Numpy-Version abzufragen. Sie können die Numpy-Version einfach mit Python-Code abfragen. Das Folgende ist die Implementierungsmethode und der Beispielcode: importnumpyasnpprint(np

See all articles