1200字范文,内容丰富有趣,写作的好帮手!
1200字范文 > js 异步执行_JS Asynchronous — JS 异步编程极简史

js 异步执行_JS Asynchronous — JS 异步编程极简史

时间:2019-05-07 00:16:33

相关推荐

js 异步执行_JS Asynchronous — JS 异步编程极简史

Asynchronous

JS 异步编程极简史,这个故事网上已经很多人有了自己的讲述。

Event Loop 解释了 Node.js 为何以及如何实现单线程服务模型和 Event Loop。对于 JS 开发者来说,我们大多数时候并不需要关心底层是如何实现异步操作的,但是我们需要了解如何进行异步编程。 JS 天然的被设计成单线程,事件循环是 JS 非常重要的特性,异步编程极其重要。

Callback

A callback is a function that is passed as an argument to another function and is executed after its parent function has completed.

JS 通过给异步操作提供一个 Callback 回调函数,当异步任务执行完成后使用操作结果(成功或失败)调用 Callback 函数,这样就可以拿到异步操作的结果。

Libuv(I/O、Timer等) —> Event Loop —> Callback 处理异步结果

setTimeout(function (msg) { // 匿名callback函数,一秒后被调用。console.log(msg); // 拿到了异步结果。}, 1000, 'hello');

Callback Hell

Node.js 的出现,使得 JS 从前端开发登上了后端开发的舞台。后端开发涉及的场景更加复杂多样,前端主要是使用 Ajax 调用后端接口,而后端则需要处理更多的业务逻辑,比如是微服务则需要更多的服务通讯。由于 JS 天然就是为浏览器端设计的,所以 JS 暴露出来的问题就越来越明显。异步处理就是其中一个经常遇到的问题,开发者往往陷入 Callback Hell 无法自拔。

当异步操作比较多或复杂(比如一个函数中包含多个网络请求),需要等前一个异步操作结束后在 Callback 中继续执行下一个异步操作,如此类推,就出现了Callback嵌套,也就是 Callback Hell。

异步操作之间的关系分为两种:

异步操作之间有前后依赖关系(串行),也就是第一个异步操作要执行完成再能继续执行下一个异步操作。比如获取某个用户收藏的文件:先执行异步操作从藏表获取用户收藏的文件,然后根据收藏的文件异步操作从数据库把文件列表取出来。异步操作之间没有前后依赖关系,也就是可以并发/并行执行,它们相互之间没有影响。比如发送邮件通知,同时发送给一百个或一万个用户之间是没有影响的,可以并发进行提高效率。

对于情况1,会产生 Callback 嵌套。对于情况二,也只能使用 Callback 嵌套处理,因为 Callback 无法保证同时并发处理多个异步操作,且在异步操作全部结束后拿到全部的结果。这就意味着 Callback 会失去批量并发处理异步请求的更高效率。

虽然使用 Callback 解决了异步编程的处理异步操作结果的问题,但是带来了 Callback Hell。

Async Thinking

在被 Callback Hell 折磨地苦不堪言的同时,开始有人探索怎样避免 Callback 的困扰。这期间出现了各种各样的解决方案,可谓百花齐放。虽然这些解决方法也有一些不足,但是比起Callback 有了很大的进步,异步代码写起来更加高效,理解起来更加易懂。除此之外,它们的设计思想值得学习。

注意 Callback 和 Callback Hell 的区别,我们不能消除 Callback(JS 异步机制就这样)。我们的目的是消除 Callback Hell,使得异步编程更加高效,异步代码更容易阅读和理解,这个是从人的角度来说。

Async

Async 是一个异步编程工具模块。

waterfall

按顺序运行 tasks 中的异步操作,每个异步操作通过callback将结果传递给下一个异步操作。

waterfall(tasks, callback)

step1、step2、step3 将按照在数组中的顺序被执行。

async.waterfall([function step1(callback) {callback(null, 'one', 'two');},function step2(arg1, arg2, callback) {// arg1 now equals 'one' and arg2 now equals 'two'callback(null, 'three');},function step3(arg1, callback) {// arg1 now equals 'three'callback(null, 'done');}], function (err, result) {// result now equals 'done'});

series

按照tasks的顺序执行数组中的异步操作。并在所有的异步操作完成后把结果数组传递给 Callback。

series(tasks, callback)

step1、 step2 执行完成后,把结果 ['one', 'two'] 传递给 Callback。

async.series([function step1(callback) {// do some stuff ...callback(null, 'one');},functionstep2(callback) {// do some more stuff ...callback(null, 'two');}],// optional callbackfunction(err, results) {// results is now equal to ['one', 'two']});

Eventproxy

EventProxy 是一个基于事件机制的异步编程工具。正如作者所说:EventProxy 仅仅是一个很轻量的工具,但是能够带来一种事件式编程的思维变化。可以说, EventProxy 是一个小而美的代码工具,推荐读一下源码,对 JS 事件的运用巧妙而有趣。

EventProxy的特点:

利用事件机制解耦复杂业务逻辑。移除被广为诟病的深度callback嵌套问题。将串行等待变成并行等待,提升多异步协作场景下的执行效率。友好的Error handling。无平台依赖,适合前后端,能用于浏览器和Node.js。

过去的,深度嵌套的,串行的异步代码。

const render = function (template, data) {_.template(template, data);};$.get("template", function (template) {// something$.get("data", function (data) {// something$.get("l10n", function (l10n) {// somethingrender(template, data, l10n);});});});

现在的,无深度嵌套的,并行的异步代码。

const ep = EventProxy.create("template", "data", "l10n", function (template, data, l10n) {_.template(template, data, l10n);});$.get("template", function (template) {// somethingep.emit("template", template);});$.get("data", function (data) {// somethingep.emit("data", data);});$.get("l10n", function (l10n) {// somethingep.emit("l10n", l10n);});

Bluebird

Promise 概念最早由E语言提出,E语言使用基于事件循环和Promise的并发模型确保永远不会发生死锁。后来JS开发者把Promise引入JS以解决异步编程callback问题,出现了不少优秀的第三方Promise代码库:JQuery的deferred、q模块和Bluebird等。官方则在ES6中加入了实现Promise/A++标准的Promise。

Promise对象可以用于表示一次异步操作的最终状态(完成或失败),以及其返回的值。比传统的解决方案 "回调函数和事件" 更合理和更强大,把异步编程从callback嵌套变成改成链式调用。更多Promise的介绍请看JS Promise。Promise在JS中出现,使得异步编程开始编程走向统一化和标准化。

Bluebird是一个第三方Promise规范实现库,它不仅完全兼容原生Promise对象,且比原生对象功能更强大。虽然ES加入了Promise,但是Bluebird依然有很多出色的功能值得去体验。

new Promise

使用Promise来封装原来使用callback的异步操作,从而实现Promise的风格处理异步代码。

const promise = new Promise(function(resolve, reject) {// 异步操作if (•••) { // 根据异步操作结果判断resolve(value); // success} else {reject(reason); // failure}});

把原本使用callback处理网络异步请求结果转换成使用then的风格。把单个异步操作封装成一个Promise对象实例,并通过then把多个异步操作改成链式调用关联起来串行处理。

function ajaxGetAsync(url) {return new Promise(function (resolve, reject) {const xhr = new XMLHttpRequest;xhr.addEventListener("error", reject);xhr.addEventListener("load", resolve);xhr.open("GET", url);xhr.send(null);});}ajaxGetAsync('/user/1').then(function(user) {return ajaxGetAsync(`/user/${user.id}/orders`)})then(function(orders) {// 请求成功的处理}).catch(function(err) {// 请求失败的处理});

Promise.promisifyAll

把对象中所有的方法全部增加一个promise化的方法。这个方法对于依然使用callback的代码很有帮助,使得我们可以使用Promise的方式去使用这些非Promise风格代码。

const Promise = require('bluebird');const fs = require('fs');const path = require('path');Promise.promisifyAll(fs);fs.readFileAsync(path.join(__dirname, 'sample.txt'), 'utf-8').then(data => console.log(data)).catch(err => console.error(err));

Promise.map

把多个异步操作转封装成Promise对象,并发处理异步代码(默认同时处理全部),并提供concurrency参数控制同时并发数量。在实际开发中,经常会遇到有大量请求要并发处理的情况,为了控制并发数,我一般选择使用concurrency参数。这个功能比较实用,但是实现起来也不难,可以自己拓展ES6的Promise中使用计数器的方式就可以实现这个功能。

Promise.map(Iterable<any>|Promise<Iterable<any>> input,function(any item, int index, int length) mapper,[Object {concurrency: int=Infinity} options]) -> Promise

并发读取三个文件的内容,三个文件读取完成后以数组的形式返回结果。

Promise.map(['1.txt', '2.txt', '3.txt'],name => fs.readFileAsync(path.join(__dirname, name), 'utf-8')).then(results => console.log(results.join(','))).catch(console.error);

ES6 Promise

官方在ES6加入了实现Promise/A++标准的Promise,统一了用法,原生提供了Promise。

其实早在社区引入Promise的时候,已经有不少代码库使用了Promise风格,等到了Promise正式加入规范,很多代码库都增加了支持Promise的版本,但是为了兼容旧的代码,也依然保存callback版本,甚至两种同时提供。

Promise虽然比callback更加强大,但是面对复杂的异步编程场景依然显得有些力不从心,代码依然出现难以编写和理解的情况。一大堆then,原来的语义变得很不清楚。

Generator

ES6同时还引入了generator函数,一种新的异步编程解决方案。

Generator也不是完全独立的一种异步编程方案,实际上是和Promise配套使用的。

// 串行执行function* render () {const template = yield $.get('template');const data = yield $.get('data');const l10n = yield $.get('l10n');_.template(template, data, l10n);};const gen = render();gen.next(); // get templategen.next(); // get datagen.next(); // get l10n// 并发执行function* render () {const [template, data, l10n] = yield [$.get('template'), $.get('data'), $.get('l10n')];_.template(template, data, l10n);

Generator automation

Generator和Promise的组合带来了更新更酷的异步编程体验,但是每一次异步操作都要手动调用next()函数,代码重复且容易出错(异步操作多的时候,数next的次数够需要耐心的)。于是TJ写了一个可以自动执行generator函数的模块co。

co(function* render () {const template = yield $.get('template');const data = yield $.get('data');const l10n = yield $.get('l10n');_.template(template, data, l10n);}).catch(err => console.error(err));

Async function

ES7引入了async函数,统一化、规范化和标准化了异步编程。Async函数使得异步编程更加容易理解,复合人类的阅读习惯。

Async函数本质上就是Generator函数的语法糖。Async函数依然是和Promise组合使用。

async function render () {const template = await $.get('template');const data = await $.get('data');const l10n = await $.get('l10n');_.template(template, data, l10n);}).catch(err => console.error(err));

参考

JavaScript 运行机制详解:再谈Event Loop关于JavaScript单线程的一些事The JavaScript Event Loop ExplainedWhat the heck is the event loop anywayJavaScript Promise迷你书

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。