Category Archives: Hello World

Hello World

搞定异步编程(二)——使用Promise管理并行程序

也算是延续三年前的一个系列,这里实测一下ES6新出的Promise对nodejs的异步编程有多大改观
还是沿用三年前那篇文章的场景,连续访问三个http接口,然后对三个结果做综合处理
话不多说,上代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var _http = require('./lib/http.js');
//对使用Promise做异步操作的封装,需要返回Promise对象,且成功回调resolve,出错回调reject
function testg(url){
    return new Promise((resolve, reject)=>{
        _http.get(url, {}, (retu)=>{
            console.log('Promise, retu, resolve', url);
            resolve(retu);
        }, (er)=>{
            console.log('Promise, er, reject');
            reject(er);
        });
    });
}
Promise.all([
    testg('https://www.baidu.com'), //一次调用三个异步请求
    testg('https://www.qq.com'), 
    testg('https://www.sina.com')
]).then((retu)=>{
    //三个异步请求的成功回调结果,封装在retu数组里
    console.log('Promise.all, retu', retu[0].length, retu[1].length, retu[2].length);
}).catch((er)=>{
    console.log('catch, er');
});

——–
转载请注明出处:http://www.jiangkl.com/2017/05/nodejs_promise

Hello World

来看一下不按常理出牌的js

某公众号上看了一篇“一道面试题引发的对JavaScript类型转换的思考”,里面提到的面试题是这样的:

实现一个函数,满足如下功能:

1
2
3
add(1)(2) // 3  
add(1, 2, 3)(10) // 16  
add(1)(2)(3)(4)(5) // 15

这么一个小功能,实现方法当然见仁见智,这里想说的js的function类型。
我面试前端的时候,经常问这么样一个问题:“jquery里的‘$’是个什么东西?”
对方不明白的话,就直接问“typeof $,会返回什么?”
能回答“function”的,就说明对方对js的数据类型是有了解的
对,$本身是个function,只是上面挂了各种属性和方法
这是第一点

第二点
是js的高阶函数
在学习闭包的时候,会用到高阶函数。所谓高阶函数,说白了就是一个函数,return的还是函数
上面的add(x)(x)(x),所以add肯定是高阶函数的形式

第三点
如果return的是函数,怎么得到相加后的数字
所以函数本身要带上toString(),或者valueOf()

好了,结合上面几点,下面是这个函数的实现,比预想的简单:

1
2
3
4
5
6
7
8
9
10
11
12
function add(){   //第一层add,第一次调用时执行
  var s0 = 0;     //闭包参数,记录累加值
  var f1 = function(){ //内层add,级联调用,实际上是调用的它
    for(var i = 0; i < arguments.length; i++) s0 += arguments[i];
    return f1;
  };
  f1.toString = function(){
    return s0;   //返回累加值
  }
  f1.apply(null, arguments);  //第一次执行时,先add一次
  return f1;
}

————-
转载请注明出处:昆仑的山头

Hello World 软硬兼施

Arduino如何多任务

小搜了一下,Arduino没有简单的多线程/多任务的解决方案
换个方向想想,也对,Arduino本身那点存储空间,就是个微控制器,专心能控制好一个东西就行了,还搞神马多任务
不过,考虑到代码的整齐度,简单的模块划分还是需要的,比如,需要同时分别控制指示灯和舵机时,你不能把所有的代码都放在Loop函数里吧
借鉴以前用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
#include <Servo.h>
Servo duoji1servo;
 
const int pinLed1   = 12;   //连接红色led
const int pinLed2   = 13;   //连接蓝色led
const int pinDuoji  = 9;    //连接舵机控制端口
 
void setup() {
  pinMode(pinLed1, OUTPUT);
  pinMode(pinLed2, OUTPUT);
  duoji1servo.attach(pinDuoji);
}
 
long _ti = 0;  //控制参数累加
void loop() {
  _ti++;
  ledTest(_ti);
  duojiTest(_ti);
  delay(1);    //1毫秒
}
 
//led闪动测试
int ledi = 0;
void ledTest(long ti){
  ledi++;
  if(ledi == 512){
      ledi = 0;
  }
  int n = ledi;
  if(ledi > 255){
    n = 512 - ledi;
  }
  analogWrite(pinLed1, n);   //分别控制LED亮度
  analogWrite(pinLed2, (255 - n));
}
 
//舵机测试
int duojii = 0;
void duojiTest(long ti){
  if(ti % 10 == 0){//调用间隔,10毫秒计算一次
    duojii++;
    if(duojii > 360){
      duojii = 1;
    }
    if(duojii <= 180){  //正传
      duoji1servo.write(duojii);
    }else{              //反转
      duoji1servo.write(360 - duojii);
    }
  }
}
Hello World 软硬兼施

Arduino的指令时间研究

习惯了服务器端编程的思路,玩硬件也想把时间搞得清楚一些
简单研究Arduino一个指令所需的时钟周期,板子是某宝上最常见的Arduino UNO R3,先上代码:

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
const int LED1  = 13;               //红色LED
const int LED2  = 12;               //蓝色LED
int loopin      = 0;
 
void setup() {
  pinMode(LED1, OUTPUT);
  pinMode(LED2, OUTPUT);
}
 
void loop() {
  loopin++;
  if(loopin % 2 == 1){              //点亮红色LED
    digitalWrite(LED1, HIGH);
    digitalWrite(LED2, LOW);
    delay(1000);                    //持续1秒
  }else{                            //点亮红色LED
    digitalWrite(LED2, HIGH);
    digitalWrite(LED1, LOW);
    delay(1000);                    //持续1秒
    //******************************关键看这里,这个for循环的时间,约等于delay(1000),此时双色同时亮起
    for(long i = 0; i < 160000; i++){
      digitalWrite(LED1, i % 2 == 0 ? LOW : HIGH);
      digitalWrite(LED2, i % 2 == 1 ? LOW : HIGH);
    }
  }
}

就这么多~~
整体上,用一个超长的for循环模拟出一个1秒的delay,然后观察同时亮起的时间,是否等于单独亮起的时间
实际测算,到16万时,同时亮起的时间,基本等于另外两个亮起的时间
(这里只是估算,有时间的话,也可以算的更精确一点:对着计数器数秒,看闪动的次数是否和秒数一直,不一致的话,就调整这个i得最大值)
也就是说,这里的一个for循环,Arduino的大约需要 1/160 毫秒,或者说0.16M,简答分析这个for循环做了哪些事情
1. i+1,= i, i与最大值比较,大约3次
2. i%2,== 0 ?, 高电平或低电平输出,大约3次
3. 与上一步基本一致
加起来大约10个指令?
Arduino UNO R3的晶振频率是16M,也就是说,大约,晶振的10个周期,可以执行一个程序指令
另一个方面,UNO的处理器核心是ATmega328,8位处理器,i是long型32位,上面的每次运算,cpu至少要算四次(具体8位处理器怎么捣腾相加两个32位的数,记不清了),按这种方式算的话,其实也差不多
—-over
转载请注明出处: 昆仑的山头

Hello World

一个外部接口引发的bug

上周开发上线了一个活动:从第三方点链接进入我们的站点,然后领取一个优惠券。使用微信oauth登录,每个用户可以领取一张券
周一,对方说,领取券后,需要给他们一个通知,使用URL上带的encrypt_code参数,调他们的一个接口~~三分钟,通知接口加进去了。当时也没多想:不就是调一个接口嘛
可是昨天看领取优惠券的数据时,发现有好多用户领取了不止一张,多的有三、四张。。。

分析问题,发现程序是先到mysql检查用户活动记录,如果没参加过,则新建优惠券、通知第三方、活动记录保存到数据库

逻辑很简单,怎么可能出问题。
再看活动记录:重复领取的记录,间隔的都很近,往往在1、2秒内
呵呵,猜到问题了吧~~给通知第三方加了日志,记录响应时间,。。。x,基本都在1秒以上
也就是说,用户进入页面、领券时,如果活动记录保存到数据库之前,再次进入活动页面,第二次进入的线程就查不到活动记录,还会继续发券~~不仅如此,这个接口本身,也拖慢了页面的响应时间,一般人看到时白页面,肯定会多刷几次
发现问题,基本也就解决了问题:
简单解决,将第三方通知放在保存活动记录之后
但是这样,页面响应时间慢的问题依然没有解决
所以,最终的解决方案是,将第三方通知改成异步进行~~

Hello World

判断文本宽度有几种方法

很多时候,我们需要判断一个字符串扔到页面上显示时的宽度,这里,按照我自己的认知顺序说一下都有哪些判断方法

初级:str.length
这个不用说,大家都明白,最简单,也最不靠谱
但是有一个地方要注意:一个中文字符,js里的length是1,而php里,根据编码不同,可以是3或2

中级:正则判断支付类型,按不同的长度处理

1
2
3
4
5
6
7
8
9
10
11
  //判断字符串的长度,全角返回2,半角返回1
  var b2reg = /[\u0000-\u00ff|\uff61-\uff9f|\uffe8-\uffee]+$/;
  function byteLen(s){
    var c = 0;
    if(s && s.length > 0){
      var a = s.split("");
      for(i in a){
        if(!b2reg.test(a[i])) c += 2;
        else c++;
      }
    }

一般要求不严格的情况,我都是这么处理
但是,这个也不靠谱,中文没问题,但是英文字符的显示宽度,会受字体、浏览器的影响,比如“i”和“w”,很难确定这俩显示宽度是否相同

终结:实际显示宽度
有些复杂,不过思路也很简单:在页面的视口之外,做一个同样的显示区域,字体、样式什么的都一样,然后把文本写进去,再判断宽度;如果过宽,再截
这种方法还有一个经典的应用:输入框跟踪提示。
比如,在一个很大的输入框里,输入多个邮箱地址,要随着用户的输入,在输入点下方位置显示不同邮箱地址的提示,这里的一个难点就是:js里无法获得当前输入点的位置。于是就可以用上面的方法,时时获取显示宽度,然后用复杂的计算近似得到当前输入点的位置…

———
(好久不写技术文章了,一方面是懒,另外很关键的一点是…近半年都在做售货机屏幕的东西,缺少普适性)

Hello World

js调试之~打印调用堆栈

(很久没写东西,这里写一个自己熟悉的内容做2016的开篇吧)
相对于其他语言,js似乎很少有需要打印调用堆栈的时候,反正我是做了这么多年了前端,第一次用到,其他情况,基本都可以通过更好的方式来调试
今天面对的问题是这样的,有一个“ad_list”事件,在不停的触发,触发它的地方有N个,触发条件也不一样,靠简单的“console.log”,太多,可读性很差,所以想到了调用堆栈
不过,由于js里大部分是匿名函数,打印出来的堆栈并不像java、php一样简介。。。,打印名字不靠谱,而是,直接打印function内容,然后靠内容反查。。。。这就是很少有人用这一招的原因

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  if(action == 'ad_list'){//打印特定条件的堆栈
    var caller = arguments.callee.caller, j = 0, s;
    while (caller) {
      j++;
      if(typeof caller == 'function' || typeof caller == 'object'){
        s = (caller + '');//function变成字符串
        //排除系统函数、框架函数
        if(s.indexOf('function (event)') < 0 && s.indexOf('function (packet)') < 0 && s.indexOf('fn.apply') < 0){
          console.log('【function】-----stack-----------', j, ': ', (caller + '').substr(0, 200));//打印funciton的前半截
        }
      }
      caller = caller && caller.caller;//取下一级
      if(j > 500){//限定数量  ---  这个一定不要漏了
        return;
      }
    }
  }

————
查了半天(真是半天)的问题,结果是自己给自己挖的坑:默认的图片/活动名称是按规约命名的,但是测试图省事没照这个规则做,才造成上面说的不停的刷新同一个东西。。。。。。

Hello World

php函数的多返回值

自从开始使用laravel,虽然没觉得这个框架本身有多好用,多“优雅”,但是还是从框架代码里学到了不少东西
首先是php命名空间的使用。php的namespace,说实话,相对于其他语言的并没有什么特色,不算好用,尤其是你的项目要include其他低版本的代码时会很麻烦
然后是数组字面量的定义方式,也就是用”[]”替代array(),可以让代码稍微简洁一点
最后值得说的,比较有价值的,就是函数的多返回值的使用
个人比较喜欢golang的一个比较大的理由就是go函数允许返回多个返回值,而php也可以利用list()函数来实现这一点

1
2
3
4
5
6
function abc($a, $b){
  //返回数组
  return [$a+$b, $a-$b];
}
list($x, $y) = abc(12, 3);//$x、$y即分别为abc()返回数组的两个值
echo $x, ', ', $y;

运行结果:15, 9
~~如上,虽然比golang稍微麻烦些,但是还是比使用&传参带回值,或者手动分解返回数组,要“优雅”的多

Hello World

使用Laravel发送邮件

以前习惯上是用phpmail,但是新的需求需要发附件,使用phpmail发送附件,总是失败,刚好看到Laravel框架自带Mail模块,于是试了试,还挺好用

1
2
3
4
5
6
7
8
9
10
$exaTitle = '邮件标题';
$datas    = array("文件名1"=> 文件流1, "文件名2"=> 文件流2);
//发送邮件
\Mail::raw ($exaTitle, function($message) use($datas, $exaTitle){
    $message->to(array('收件人数组'))->subject ($exaTitle);
    foreach($datas as $k => $v){
        \Log::info('【SendPhpExcel】--add mail:'.$k.'--length('.count($v).')');
        $message->attachData($v, $k);
    }
});

$message->attachData(),用来发送文件流,如果是已经存放到硬盘上的文件,可以用$message->attach($filePath)
——
over

Hello World 倒塌集

2147483647

如果你在你的mysql里看到了这个数,一定稍微停下来想想
两周前写的同步联系人的方法,定时执行,半小时一次,已有的更新,没有的插入新数据

1
2
3
4
5
6
7
8
9
10
11
12
13
if(count($contacts) > 0){
    foreach($contacts as $co){
        $c = Contacts::where('uin', $co['Uin'])->first();
        if(empty($c)){
            $c = new Contacts();
            $c->uin = $co['Uin'];
        }
        $c->nick_name    = $co['NickName'];
        $c->user_name    = $co['UserName'];
        $c->last_fri_uid = $friUin;
        $c->save();
    }
}

很简单地逻辑吧~~
因为是demo项目,写完以后正常跑着,就没怎么细看
今天看Contacts库里面。。。。。。x,已经十几万条数据了
可以实际上只有几十个联系人,稍微细看,NickName全是重的
当时就对着这段程序找bug,加日志,但是,真没发现问题

在仔细看错误的数据,uin字段都是2147483647。。。好像猜到问题了。
于是百度mysql int字段的长度,恩,2147483647正式int的最大长度,如果给更大的数,存进去的还是这个~~与你建表是给这个字段的length无关
而这里的uin,正是这样一个长度不定的字段
好吧,这坑掉的值
字段改成bigint,或者varchar都ok,考虑的uin是外部传来的数据,还是varchar靠谱些
然后,“date -r 2147483647”,返回“2038年 1月19日 星期二 11时14分07秒 CST”,呵呵,这就是所谓的2038年问题
以前时间也都是用int保存的,以后再用到的时候,要不要都改成bigint字段?~~~思考中