前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >async/await 带你逃离回调地狱

async/await 带你逃离回调地狱

原创
作者头像
Gcaufy
修改于 2017-06-19 10:58:20
修改于 2017-06-19 10:58:20
2.2K00
代码可运行
举报
文章被收录于专栏:Gcaufy的专栏Gcaufy的专栏
运行总次数:0
代码可运行

回调地狱

一个段子

以前有个段子讲一个小偷,潜入某神秘机构,偷出代码最后一页,打开一看:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
                                        });
                                    });
                                });
                            });
                        });
                    });
                });
            });
        });
    });
});

什么?这只是段子不是现实?那看看现实版快滴打车的源代码

因为Javascript的异步特性,每个开发者都无法避免会碰到一些callback hell,同时在代码的迭代过程当中因为这样一些callback hell导致代码越来越不可维护。尤其是当回调过程中去参杂一些同步逻辑判断,那都是迭代过程中的代码杀手。

一个例子

产品:我们需要从服务器取数据,然后再xxx

开发:搞定

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function myFunc() {
	getServerData(function () {
		// Do something
	});
}

产品:我们要插个小功能,取另一分数据,然后再xxx

开发:ok

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function myFunc() {
	getServerData(function (d1) {
		getServerData2(function (d2) {
			// Do something
		});
	});
}

产品:需要在取第二份数据前加个小判断,部分用户不需要取第二份数据。

开发:改起来会有点麻烦。

产品:不就加个条件判断么?怎么会麻烦。

开发:...

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function myFunc() {
	getServerData(function (d1) {
	    var doSomething = function () {
	        // Do something
	    }
	    if (condition) {
	        getServerData2(function (d2) {
	            doSomething();
	        });
	    } else {
	        doSomething();
	    }
	});
}

产品:再帮我加一个很小的功能。

开发:...

解套平坑

解决方案

其实JavaScript 一直在避免回调地狱的问题做出努力,比如async.jsthen.js包括ES6下的Promiseco generator等等。这里不去细讲,想进一步了解这些解决方案的差异的话可以看尤雨溪大神在直呼上的回答:

nodejs异步控制「co、async、Q 、『es6原生promise』、then.js、bluebird」有何优缺点?最爱哪个?哪个简单?

Async Functions

这里要讲的是一种更平滑更接近同步体验的一种方案Async Functions

async/await语法最早是在C#5.0中引入,引入后引起了一致好评,因此使用异步编程最多的JavaScript迫不及待的向ES2016(ES7)提交了草案,但因为某些原因,呼声很高的Async Functions并没能赶上ES2016的deadline,估计最晚会在ES2017中加入到正式规范,但是并不妨碍我们在Babel的帮助下在ES5的环境下使用它。

先看看在使用Async Functions的情况下,上面产品需求的代码开发将会怎么实现:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
async function myFunc () {
    let d1, d2;
    d1 = await getServerData();
    if (condition) {
        d2 = await getServerData2();
    }
    // Do something
}

加入了神奇的asyncawait关键字后,上面的异步回调完全以同步的方式展现,也不用去担心产品需要再在某个回调中插入流程了而且导致代码结构大面积改动了。

Babel实现方式

babel编译Async Functions需要transform-async-to-generator插件,参考官方文档安装。

基于ES6

写上测试代码src/index.js

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function sleep(time) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(Math.random() * 10 >> 0);
        }, time * 1000);
    });
}

async function main () {
    console.log('Random is: ');
    let d = await sleep(2);
    console.log(d);
    return d;
}

let rst = main();

babel配置文件.babelrc如下:

代码语言:txt
AI代码解释
复制
{
  "plugins": ["transform-async-to-generator"]
}

执行命令编译:

代码语言:bash
AI代码解释
复制
babel src -d dist

编译后主要代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
let main = (() => {
    var _ref = _asyncToGenerator(function* () {
        console.log('Random is: ');
        let d = yield sleep(2);
        console.log(d);
        return d;
    });

    return function main() {
        return _ref.apply(this, arguments);
    };
})();

编译后的代码和co很相似,可以理解为基于ES6的PromiseGenerator的语法糖。

基于ES5

因此要进一步运行在浏览器环境下我们还需要使用ES2015 presetstransform-runtime插件。

babel配置文件.babelrc如下:

代码语言:txt
AI代码解释
复制
{
  "presets": ["es2015"],
  "plugins": ["transform-async-to-generator", "transform-runtime"]
}

编译后关键代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var main = function () {
    var _ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee() {
        var d;
        return _regenerator2.default.wrap(function _callee$(_context) {
            while (1) {
                switch (_context.prev = _context.next) {
                    case 0:
                        console.log('Random is: ');
                        _context.next = 3;
                        return sleep(2);

                    case 3:
                        d = _context.sent;

                        console.log(d);
                        return _context.abrupt('return', d);

                    case 6:
                    case 'end':
                        return _context.stop();
                }
            }
        }, _callee, this);
    }));

    return function main() {
        return _ref.apply(this, arguments);
    };
}();

可以看到编译后代码是由状态机实现,并无任何ES5下不兼容代码。

使用与实践

错误捕捉

在使用Promise时,我们有resolvereject,如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function doSth() {
	promise().then(d => console.log(d)).cache(e => console.error(e));
}

Async Functions中写法如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
async doSth() {
	try {
		let d = await promise();
		console.log(d);
	} catch (e) {
		console.error(e);
	}
}

async是使用throw相当于Promise中的reject:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
async function hello() {
	await sleep(5);
	throw Error('err');
}
let promise = hello();
promise.then(d => console.log(d)).catch(e => console.log(e));
// 输出err

返回值

Async函数中, 返回值永远是Promise

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
async function hello() {
	await sleep(5);
	return 'world';
}
let promise = hello();
promise.then(d => console.log(d));
// 输出world

循环中使用async

因为同步非阻塞的表现,所以在循环中使用async将会比以前的代码更易读明了。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
async function getResponseSize(url) {
	const response = await fetch(url);
	const reader = response.body.getReader();
	let result = await reader.read();
	let total = 0;
	
	while (!result.done) {
		const value = result.value;
		total += value.length;
		console.log('Received chunk', value);
		result = await reader.read();
	}

	return total;
}

匿名函数中使用

同样在匿名函数中可以一样去使用async关键字,如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const promises = urls.map(async url => {
	const p = await fetch(url);
	return p;
});

await连续使用问题

代码一:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
async function foo() {
	await sleep(3);
	await sleep(3);
	return 'done';
}

运行完需要6秒。

代码二:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
async function bar() {
	const s1 = sleep(3);
	const s2 = sleep(3);
	await s1;
	await s2;
	return 'done';
}

代码二运行完却只要3秒,因为sleep是在同一时间运行的。

结束语:async/await 无疑是现阶段最好的异步回调同步化的解决方案,不过因为暂时没有纳入ES2016规范,而且主流浏览器的支持的不足,所以我们只能通过使用babel尝鲜。但是我们也可以借此看到未来JavaScript在回调问题上的主流解决方案。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
async/await 源码实现
如果你有一个这样的场景,b依赖于a,c依赖于b,那么我们只能通过promise then的方式实现。这样的的可读性就会变得很差,而且不利于流程控制,比如我想在某个条件下只走到 b 就不往下执行 c 了,这种时候就变得不是很好控制!
用户4131414
2020/03/19
1.3K0
Promise 向左,Async/Await 向右?
1. 前言 从事前端开发至今,异步问题经历了 Callback Hell 的绝望,Promise/Deffered 的规范混战,到 Generator 的所向披靡,到如今 Async/Await 为大众所接受,这其中 Promise 和 Async/Await 依然活跃代码中,对他们的认识和评价也经历多次反转,也有各自的拥趸,形成了一直延续至今的爱恨情仇,其背后的思考和启发,依旧值得我们深思。 预先声明: 本文的目标并不是引发大家的论战,也不想去推崇其中任何一种方式来作为前端异步的唯一最佳实践,想在介绍下
用户1097444
2022/06/29
5230
Promise 向左,Async/Await 向右?
如何区分 babel polyfill 和 transform-runtime
从最终的行为来解释就是说babel引入了helper工具函数自动来执行polyfill并且不污染全局作用于,采用require的方式。
上山打老虎了
2022/06/14
4060
「Async/Await」仅仅了解使用?这次我们来聊聊它是如何被实现的
这篇 Async 是如何被实现的,其实断断续续已经在草稿箱里躺了很久了。终于在一个夜黑风高的周六晚上可以给他画上一个句号。
19组清风
2022/02/28
8470
「Async/Await」仅仅了解使用?这次我们来聊聊它是如何被实现的
面试官: 说说你对async的理解
async是generator和promise的语法糖,利用迭代器的状态机和promise来进行自更新!
用户8200753
2023/10/22
2250
async/await 一种非常丝滑的异步语法
---- async是es7提出的一种新的异步语法. 一开始es为了解决异步,使用的是promise, 但看到满屏的then之后,就感觉自己傻逼了. 后来提出了generator, 在底层实现了一个异步的模式, 但需要手动执行. 关于如何使用generator,可以参考,how to use generator. 本文这里,不探讨怎么使用generato. 而是,如果使用generator和promise 构造出async的表达. 文末后面会介绍如何正式的使用 async/await,以及里面有什么需要注意
villainhr
2018/07/03
6690
Promise/async/Generator实现原理解析
笔者刚接触async/await时,就被其暂停执行的特性吸引了,心想在没有原生API支持的情况下,await居然能挂起当前方法,实现暂停执行,我感到十分好奇。好奇心驱使我一层一层剥开有关JS异步编程的一切。阅读完本文,读者应该能够了解:
Nealyang
2020/03/25
1.9K0
async与await的原理揭秘
async和await是es7语法,在babel中会被转译,我们看一下专一前和转译后的源码:
挥刀北上
2021/12/10
8510
async与await的原理揭秘
async/await 回调地狱解决方案
在 js 异步编程中,通过回调函数实现 当多个异步逻辑间产生顺序或关联逻辑,就会产生回调嵌套(回调地狱),导致代码丑陋且难以阅读,形如:
lukachen
2023/10/22
3060
手写async await的最简实现(20行)
如果让你手写async函数的实现,你是不是会觉得很复杂?这篇文章带你用20行搞定它的核心。
ssh_晨曦时梦见兮
2020/04/11
1.5K0
什么是回调地狱?如何解决回调地狱问题_地狱回调
这个问题呢,需要从Node.js的API说起,这里就会有人问了?博主你不是说回调地狱的问题吗,怎么说到API了,别急,看博主一步一步的解释给你听:
全栈程序员站长
2022/11/15
3.5K0
Javascript异步回调细数:promise yield async/await
虽然我对js的鄙视一直都是无以复加,但是奈何前端环境不得不依赖javascript。哪些nodejs的大神们四处布道nodejs统治一切:单线程非阻塞,高IO操作。但是,java也可以做好吧,而且GO做的更干练!假设你的应用程序要做两件事情,分别是A和B。你发起请求A,等待响应,出错。发起请求B,等待响应,出错。Go语言的阻塞模型可以非常容易地处理这些异常,而换到了Node里,要处理异常就要跳到另一个函数里去,事情就会变得复杂。
周陆军博客
2023/05/07
9040
异步多图加载这件小事儿(Promise与async)
日常开发过程中,时不时会遇到要同时预加载几张图片,并且等都加载完再干活的情况,结合 Promise 和 async/await 代码会优雅很多,但也容易遇到坑,今天就来简单聊聊。 ES5 先从最基本的 ES5 说起,基本思路就是做一个计数器,每次 image 触发 onload 就加一,达到次数后触发回调函数。 var count = 0, imgs = []; function loadImgs(imgList, cb) { imgList.forEach(function(url,
Bob.Chen
2018/05/02
2.5K0
异步多图加载这件小事儿(Promise与async)
跨越时空的对白——async&await分析
在ES6中新增了asgnc...await...的异步解决方案,对于这种方案,有多种操作姿势,比如这样
Yerik
2022/05/08
1.2K3
跨越时空的对白——async&await分析
微信小程序中异步处理终极方案async/await
Promise和co都搞过了,终于还是忍不住要折腾,上ES7的终极方案:async/await。
一斤代码
2018/08/21
5.2K0
微信小程序中异步处理终极方案async/await
JavaScript 异步解决方案 async/await
异步操作一直都是 JavaScript 中一个比较麻烦的事情,从最早的 callback hell,到TJ大神的 co,再到 Promise 对象,然后ES6中的 Generator 函数,每次都有所改进,但都不是那么彻底,而且理解起来总是很复杂。
李振
2021/11/26
4140
4. 精读《AsyncAwait 优越之处》
本期精读的文章是:6 Reasons Why JavaScript’s Async/Await Blows Promises Away
黄子毅
2022/03/14
3300
4. 精读《AsyncAwait 优越之处》
async/await剖析
JavaScript是单线程的,为了避免同步阻塞可能会带来的一些负面影响,引入了异步非阻塞机制,而对于异步执行的解决方案从最早的回调函数,到ES6的Promise对象以及Generator函数,每次都有所改进,但是却又美中不足,他们都有额外的复杂性,都需要理解抽象的底层运行机制,直到在ES7中引入了async/await,他可以简化使用多个Promise时的同步行为,在编程的时候甚至都不需要关心这个操作是否为异步操作。
WindRunnerMax
2020/08/27
3410
JavaScript 的 async/await : async 和 await 在干什么
async 是“异步”的简写,而 await 可以认为是 async wait 的简写。 async 用于申明一个 function 是异步的,而 await 用于等待一个异步方法执行完成。 await 只能出现在 async 函数中。
一个会写诗的程序员
2018/08/17
9930
JavaScript 的 async/await : async 和 await 在干什么
从Generator到Async function
为什么说Async function是从Promise,Generator一路走来的?
ayqy贾杰
2019/06/12
5370
相关推荐
async/await 源码实现
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档