2018/03/29

Generators

目前我學的三個語言都有支援 generators,一般來說我們會用 foreach 去跑 Iterator,假設我們創建了一個極大的 array variable,非常佔用記憶體,而 generators 的出現就是解決這個問題,當你真的要取值時才使用 yield 吐出資料,最常見的範例就是爬檔案了,一般寫法是把檔案內容整個讀進來 memory,然後你在對他進行 iterator 的訪問,假設該檔案有五萬行資料,你的東西在第十筆就找到了,那剩下的四萬多筆資料只是單純浪費記憶體空間而已,接下來實做三個語言的 generators 用法

PHP

<?php

function go($length)
{
    for ($i = 0; $i < $length; $i++) {
        yield $i;
    }
}

foreach (go(10) as $value) {
    echo $value, PHP_EOL;
}

node.js

function* go(length) {
    for (let i = 0; i < length; i++) {
        yield i;
    }
}

for (value of go(10)) {
    console.log(value);
}

python

def go(length):
    for value in range(10):
        yield value


for value in go(10):
    print(value)

上面的程式執行後都會得到 0-9,而 python 在這個部份有很多變化,像是可以用 xrange 這個 function 拿到一個 generator 的結果,寫法也比較多變化

x = [v for v in range(10)]
print(x)

# [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

x = (v for v in range(10))
print(x)

# <generator object <genexpr> at 0x7fe2b60916e0>

for value in x:
    print(value)

# 0 1 2 3 4 5 6 7 8 9

2018/03/22

Async Await

ndoe.js 預設的功能都是非同步的,一般來說程式語言都是依序從上執行到下,而 node.js 的話不是,我們來看一下一個簡單的範例

const runTimeout = () => {
    setTimeout(() => {
        console.log('time is up');
    }, 2000);
};

runTimeout();
console.log('done');

以往我們的想像會是先拿到 time is up 在拿到 done,但這個 case 的執行結果會先拿到 done 再拿到 time is up,這樣的優點是你可以一次同時處理很多事情,拿到結果後進行下一步,之前要達到這樣的效果,要寫很多程式碼,然而現實生活中其實大多的功能都必須依序執行,所以有了 promise 來解決這件事情,網路上範例很多,我這邊會用最精簡的作法當作是個人的筆記。

以上方例子來說,修改方式如下:

const runTimeout = async (time) => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(time);
        }, time);
    });
};

const main = async () => {
    try {
        const r1 = await runTimeout(2000);
        const r2 = await runTimeout(1000);

        console.log(r1);
        console.log(r2);
        console.log('done');
    } catch (e) {
        console.error(e);
    }
};

main();

我將程式買改寫成可帶入參數,這樣可以驗證他的確是依序做完的,執行結果為:

2000
1000
done

另一個情境是,執行數量是動態的話要怎麼實現,範例如下:

const runTimeout = async (time) => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(time);
        }, time);
    });
};

const times = [
    3000,
    1000,
    2000
];

const main = async () => {
    try {
        Promise.all(times.map(runTimeout)).then((msg) => {
            console.log(msg);
            console.log('done');
        });
    } catch (e) {
        console.error(e);
    }
};

main();

執行結果:

[ 3000, 1000, 2000 ]
done

第二種作法:

const runTimeout = async (time, name) => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(`run ${name} in ${time} secs`);
        }, time);
    });
};

const times = [
    [3000, 'a'],
    [1000, 'b'],
    [2000, 'c']
];
const runners = times.map(async (item) => {
    const result = await runTimeout(item[0], item[1]);

    return result;
});
const main = async () => {
    try {
        Promise.all(runners)
            .then((msg) => {
                console.log(msg);
                console.log('done');
            });
    } catch (e) {
        console.error(e);
    }
};

main();

執行結果:

[ 'run a in 3000 secs',
  'run b in 1000 secs',
  'run c in 2000 secs' ]
done