说是iOS转React Native,不如说是iOS转JavaScript最需要转变的思路。
本质上来说JavaScript是单线程语言;同时,可能因为它是为网络而生,需要高频度地向服务器读取数据;再者,可能因为它所在的客户端——浏览器——的执行效率并不高;因此,它采取了大量的异步化操作。例如,要装载图片,需要有一个异步读取图片的过程;要读取文件/数据库,也必须异步读取;可能最令iOS程序员发指的是,如果要使用原生的iOS模块,那么必然会要用异取读取。
所以,原来简单的iOS程序,就会变得支离破碎。就拿读文件来说,在iOS程序中,你只需要在一个模块中按顺序写下来就好:
1)获得文件路径
2)打开文件,读取文件内容(若读取时间长,顶多来一个UI转圈界面展示)
3)使用文件内容
转到RN中呢,你需要
1)获得文件路径
2)开一个子线程去读文件
3)准备一个回调(callback)函数,等待子线程读取后再返回
看起来好像没有什么区别,但是,由于JS是单线程函数,你还不能在主线程中单纯等待返回。真实的情况是,主线程执行完了,才会去执行子线程。因此,一个简单的读取文件的过程,就不得不写成好像从服务器异步读取数据一样麻烦。
单是读取文件也罢了,若是你写了一个iOS原生模块,是各种工具函数的集合,然后会在RN模块中反复调用。那么,你就需要在等待一个函数返回之后,再调用另一个函数,于是,在多次调用之后,你发现已陷入到传说中的“回调地狱”!
说实话,这是我从iOS编程转向RN的过程中,最不习惯的一道坎!比起编程环境搭建,比起各种模块的调用方式,这个思路上的转变最让人闹心。
最后,还是找到一些相对简单的解决方法。以下这篇文章给出了最有用的一种方法:
http://blog.csdn.net/kunshan_shenbin/article/details/40425143
简单说,就是用特定的语法方式,让编写代码的过程,变得不再大括号套小括号。以下是这种方法使用的简单流程。
1.在文件头引入Generator、以及next控制函数的工具性代码
// 当前的 Generator
let activeGenerator;
// 处理 g.next() 功能
function gNext() {
return function(err, data) {
if(err) {
throw err;
}
// g.next(),并把回调函数的结果作为参数传递给 yield
activeGenerator.next(data)
}
}
// 控制工具
function gQueue(generatorFunc) {
activeGenerator= generatorFunc(gNext());
activeGenerator.next();
}
2.异步调用的时候使用这种语法开头
// 该语句实际产生了一个generator函数,并定义了其中的next操作
gQueue(function * flow(next) {
“flow”这个名字是随便取的,这里表示它是一个流程控制工具,该名字不会被再次调用。接下来,我们在gQueue中,就能大胆地像写同步语法一样,使用异步调用了:
let result1 = yield (callBack => {
someModule.someFunc1(var1, var2..., callBack);
})(next);
// 以下语句会在result1获得值后继续执行
let result2 = yield (callBack => {
someModule.someFunc2(result, var2..., callBack);
})(next);
实际上,由于generator函数的特性,每次.next()调用,都会来到新的一条yield语句。然后,该函数所在线程会暂停,开始调用yield语句中的匿名函数。在匿名函数中,我们放置了自己所想要调用的异步操作:someModule.someFunc。
之后的方式非常巧妙,注意,上述匿名函数所使用的形参callBack,在调用的时候,被实参 next 替代。因此,callBack返回,实际上是调用了 next 函数。而根据最前面的工具定义,next 对应的是 gNext() 函数【JS的语法这个地方有点绕,需要多看几遍】,因此,callBack函数中,定义的第一个参数 error 用来描述返回结果是否有误,而第二个参数data,则被当成返回值传给了yield。那么,我们的 result 就获得了这个data值。
更巧妙的是,当 g.next(data)除了返回data值,实质上还执行了一次.next操作。那么,generator函数所有线程将继续执行,这样就保证了后续语句立刻会在返回后运行。实现了看似“同步连续执行”的效果。
如果对以上机制仍感模糊,阮一峰老师的这篇文章值得推荐:Generator 函数的语法。
3.回调函数callBack的特殊规则
根据以上机制,要正确地使用该方法,必须对我们使用的callBack使用特定的返回机制。callBack的第一个参数 error 必须用来描述返回结果是否有误,而第二个参数data则用于返回真正需要的数据。假如你返回的数据有多个,那么必须打包成一个数据,然后在返回值里再做反向解析。以下是这一callBack机制的正确使用方式:
function someFunction(var1, var2, callback) {
// some operation,最后得到两个值要返回
let result1 = ...;
let result2 = ...;
callback(null, [result1, result2]);
}
最后的语句中,偷懒把错误值定成了null,待返回的两个值,包装成一个数组作为data返回过去了。那么后面调用时,获得这个两个值,就必须分别从结果中以数组方式解析。
4.其它
这篇文章只是描述了异步调用的一个最大障碍。相关的问题还有 setTimeout 函数的应用,以及针对异步模块调用的监听等技术方案,将来有空再来细述。