原文链接:http://aisk.me/using-async-await-to-avoid-callback-hell/
JavaScript 中最蛋疼的事情莫过于回调函数嵌套问题。以往在浏览器中,因为与服务器通讯是一种比较昂贵的操作,因此比较复杂的业务逻辑往往都放在服务器端,前端 JavaScript 只需要少数几次 AJAX 请求就可拿到全部数据。
但是到了 webapp 风行的时代,前端业务逻辑越来越复杂,往往几个 AJAX 请求之间互有依赖,有些请求依赖前面请求的数据,有些请求需要并行进行。还有在类似 node.js 的后端 JavaScript 环境中,因为需要进行大量 IO 操作,问题更加明显。这个时候使用回调函数来组织代码往往会导致代码难以阅读。
现在比较流行的解决这个问题的方法是使用 Promise,可以将嵌套的回调函数展平。但是写代码和阅读依然有额外的负担。
另外一个方案是使用 ES6 中新增的 generator,因为 generator 的本质是可以将一个函数执行暂停,并保存上下文,再次调用时恢复当时的状态。co 模块是个不错的封装。但是这样略微有些滥用 generator 特性的感觉。
ES7 中有了更加标准的解决方案,新增了 async/await 两个关键词。async 可以声明一个异步函数,此函数需要返回一个 Promise 对象。await 可以等待一个 Promise 对象 resolve,并拿到结果。
比如下面的例子,以往我们无法在 JavaScript 中使用常见的 sleep 函数,只能使用 setTimeout 来注册一个回调函数,在指定的时间之后再执行。有了 async/await 之后,我们就可以这样实现了:
async function sleep(timeout) {
return new Promise((resolve, reject) => {
setTimeout(function() {
resolve();
}, timeout);
});
}
(async function() {
console.log('Do some thing, ' + new Date());
await sleep(3000);
console.log('Do other things, ' + new Date());
})();
执行此段代码,可以在终端中看到结果:
Do some thing, Mon Feb 23 2015 21:52:11 GMT+0800 (CST)
Do other things, Mon Feb 23 2015 21:52:14 GMT+0800 (CST)
另外 async 函数可以正常的返回结果和抛出异常。await 函数调用即可拿到结果,在外面包上 try/catch 就可以捕获异常。下面是一个从豆瓣 API 获取数据的例子:
var fetchDoubanApi = function() {
return new Promise((resolve, reject) => {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
var response;
try {
response = JSON.parse(xhr.responseText);
} catch (e) {
reject(e);
}
if (response) {
resolve(response, xhr.status, xhr);
}
} else {
reject(xhr);
}
}
};
xhr.open('GET', 'https://api.douban.com/v2/user/aisk', true);
xhr.setRequestHeader("Content-Type", "text/plain");
xhr.send(data);
});
};
(async function() {
try {
let result = await fetchDoubanApi();
console.log(result);
} catch (e) {
console.log(e);
}
})();
ES7 还在草案阶段,那现在想用这个特性怎么办?可以尝试 google 的一个 JavaScript 预编译器 traceur,可以将高版本的 JavaScript 编译为 ES5 代码,已经实验性的支持了 async/await (需要使用 --experimental 来指定开启)。traceur 可以直接在后端使用,也可以在浏览器中使用。另外如果只在 node.js 环境中使用的话,还有一些 polyfill 模块,比如这个。
本文链接:JavaScript ES7 中使用 async/await 解决回调函数嵌套问题,转载请注明。
找出程序的调用堆栈 trace 可以知道是谁调用了这个接口,也能快速学习程序的调用流程,非常实用。但需要注意的是,不能在正式代码中使用,只能用于调试,这个非常耗资源也会造成 log 泛滥。
下面就介绍如何在 Android Java/C++/C 程序当中打印出程序调用 trace,如果需要在其他环境中使用的话 C++/C 部分需要移植 corkscrew 库。
Java
非常简单,创建一个 Throwable
对象,就可以得到当前的 stack trace。下面例子是打出调用 foobar()
函数的 trace:
2 Throwable t = new Throwable();
3 Log.d(TAG, "stack trace is ", t);
4 }
C++
也比较简单,使用 utils/Callstack
类即可。头文件位于 frameworks/native/include/utils/CallStack.h
,一般无需修改 Android.mk 可直接使用。下面例子是打出调用 Foo::bar()
函数的 trace:
2
3 void Foo::bar() {
4 // CallStack::CallStack(const char* logtag, int32_t ignoreDepth, int32_t maxDepth)
5 CallStack *t = new CallStack("Trace", 1, 30);
6 delete t;
7 }
C
稍微麻烦一点,需要直接调用 corkscrew/backtrace
。其实 C++ 里的 utils/Callstack
也是使用 corkscrew/backtrace
,只是进行了封装更易于使用。我们参照 CallStack.cpp
里面代码即可。下面例子是打出调用 foobar()
函数的 trace:
NOTE: C 不能直接调用 C++ 代码,除非在 C++ 类中添加相应的 C wrapper,或者通过 dlsym 动态载入。
2
3 void dumpStackTrace(const char* logtag, int32_t ignoreDepth, int32_t maxDepth) const {
4 static const int MAX_DEPTH = 31;
5 static const int MAX_BACKTRACE_LINE_LENGTH = 800;
6
7 if (maxDepth > MAX_DEPTH) {
8 maxDepth = MAX_DEPTH;
9 }
10 backtrace_frame_t mStack[MAX_DEPTH];
11 ssize_t count = unwind_backtrace(mStack, ignoreDepth + 1, maxDepth);
12 if (count <= 0) {
13 LOGE("Can not get stack trace");
14 return;
15 }
16
17 backtrace_symbol_t symbols[count];
18
19 get_backtrace_symbols(mStack, count, symbols);
20 for (size_t i = 0; i < count; i++) {
21 char line[MAX_BACKTRACE_LINE_LENGTH];
22 format_backtrace_line(i, &mStack, &symbols,
23 line, MAX_BACKTRACE_LINE_LENGTH);
24 ALOG(LOG_DEBUG, logtag, "%s%s",
25 "",
26 line);
27 }
28 free_backtrace_symbols(symbols, count);
29 }
30
31 void foobar() {
32 dumpStackTrace("Trace", 1, 30);
33 }
头文件位于 system/core/include/corkscrew/backtrace.h
,在 Android.mk 中还需要加入:
本文链接:如何打出Android程序调用stack trace,转载请注明。
没有评论:
发表评论