[컴][js] 자바스크립트 code 의 속도 측정

js code speed / 코드 속도 측정 / js engine speed / 벤치마크 / benchmark


js code 의 속도 측정 사이트

위의 사이트에서 특정 js code 를 적고, 실행해서 소요시간을 측정할 수 있다.

특징

  1. 위의 방법을 이용해서 손쉬게 firefox / chromium 의 속도 비교를 할 수 있다.
  2. 다만 주의할점은 처음 수행할 때와 두번째 수행할 때의 속도가 다를 수 있다. 그러므로 여러번 해보고 나서 속도를 판단할 필요가 있다.

[컴] React Native 의 단점과 Flutter 의 장점


Flutter 의 장점

reddit 에 올라온 댓글 (Flutter: A Framework to Surpass React Native : programming) 을 보면, react native 의 단점을 엿볼 수 있다. 더불어 Flutter 의 장점도 알 수 있다.

react native 의 단점

글을 보면 Flutter 가 여러모로 나아보인다.

See Also

  1. Kotlin/Native: kotlin 진영에서도 여러 플랫폼에서 작성할 수 있도록 KMM 이라는 것을 내놨다.
  2. Bringing PC Games to Mobile with Flutter | Rainway
  3. Why Flutter Uses Dart - By
  4. React Native at Airbnb: The Technology | by Gabriel Peal | Airbnb Engineering & Data Science | Medium, 2018-06-19
    1. Airbnb(에어비앤비) 에서 React Native 를 사용하면서 얻을 내용을 공유했다. Native 의 단점들을 세세하게 적어놨다. Airbnb 는 결국 서서히 React Native 를 사용하지 않게 된 이유를 적어놨다. 
    2. 몇줄정리
      1. 처음에 cross-platform에 대한 대처를 쉽게 하기 위해서 사용했다.
      2. 결국 높은 단계의 feature 를 제공하기 위해선 native 를 사용하게 된다. 이것이 처음에 의도했던 하나의 코드로 여러 플랫폼에서 사용하는 것이 더 이상 불가능하게 됐다.
      3. 개발자도 reactive native 에 대한 지식에 더해, iOS, androidOS 에 대한 지식을 갖추어야 했다. 팀은 결국 쪼개질 수 밖에 없었다.
      4. 결국 native 에서 사용할 framework 를 만들었다.
  5. Reflectly — From React Native to Flutter | by Daniel Vestergaard | Reflectly Engineering | Medium
    1. react native 는 결국 android, ios 에서 원하는 UI 모습을 표현하지 못했다. Flutter로는 만족스럽다. 라는 내용의 글

[컴][js] redux-saga 동작 분석




redux-saga 동작 분석

위의 예제로 분석할 것이다.

// saga.js
import { put, takeEvery, all  } from 'redux-saga/effects'

const delay = (ms) => new Promise(res => setTimeout(res, ms))


export function* helloSaga() {
  console.log('Hello Sagas!')
}


// ...

// Our worker Saga: will perform the async increment task
export function* incrementAsync() {
  yield delay(1000)
  yield put({ type: 'INCREMENT' })
}

// Our watcher Saga: spawn a new incrementAsync task on each INCREMENT_ASYNC
export function* watchIncrementAsync() {
  yield takeEvery('INCREMENT_ASYNC', incrementAsync)
}



// notice how we now only export the rootSaga
// single entry point to start all Sagas at once
export default function* rootSaga() {
  yield all([
    helloSaga(),
    watchIncrementAsync()
  ])
}


// main.js
import "babel-polyfill"

import React from 'react'
import ReactDOM from 'react-dom'
import { createStore, applyMiddleware } from 'redux'

import Counter from './Counter'
import reducer from './reducers'
//// default export 인 sagaMiddlewareFactory 를 createSagaMiddleware 에 assign 한 것
import createSagaMiddleware from 'redux-saga'
// import { rootSaga } from './sagas'
import rootSaga from './sagas'

// const store = createStore(reducer)
const sagaMiddleware = createSagaMiddleware()
const store = createStore(
  reducer,
  applyMiddleware(sagaMiddleware)
)
sagaMiddleware.run(rootSaga)

const action = type => store.dispatch({type})

function render() {
  ReactDOM.render(
    <Counter
      value={store.getState()}
      onIncrement={() => action('INCREMENT')}
      onDecrement={() => action('DECREMENT')}
      onIncrementAsync={() => action('INCREMENT_ASYNC')} />,
    document.getElementById('root')
  )
}

render()
store.subscribe(render)

createSagaMiddleware()

createSagaMiddleware() --> sagaMiddlewareFactory() 가 호출된다. sagaMiddlewareFactory 를 확인해보면 대체로 이전버전의 사용법에 대한 error 를 던져주기 위한 용도인듯 하다. 그외에는 sagaMiddleware 라는 function 을 만들어서 return 해준다.

sagaMiddleware() 를 호출할 때 runSaga 에 첫번째 argument 를 assign 해준다.(이것이 boundRunSaga) 그리고 sagaMiddleware.run 을 하면,  이 boundRunSaga  를 호출한다.


createStore() / applyMiddleware()

applyMiddleware() 는 단순하다. 기존의 createStore() 를 실행하고, 그 이외에 다른 동작을 하기 위한 함수라고 보면 된다. python 의 decorator 같은 역할이다.

물론 로직은 좀 더 복잡하게 짜여있다. applyMiddleware() 를 직접실행시키는 것이 아니라createStore 내에서 applyMiddleware() 가 decorator 처럼 동작해야 해서 createStore 내에서 다시 호출(call)한다. 그런 이유로 applyMiddleware() 가 function 을 return 한다. 말이 복잡하다. source code 를 확인하자.

export default function applyMiddleware(...middlewares) {
  return createStore => (...args) => { // pass the createStore as an argument
    const store = createStore(...args)
    let dispatch = () => {
      throw new Error(
        'Dispatching while constructing your middleware is not allowed. ' +
          'Other middleware would not be applied to this dispatch.'
      )
    }

    const middlewareAPI = {
      getState: store.getState,
      dispatch: (...args) => dispatch(...args)
    }
   // 기존의 dispatch 에 middleware 를 추가해서 dispatch 를 새롭게 만든다.
    const chain = middlewares.map(middleware => middleware(middlewareAPI))
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}
...
export default function createStore(reducer, preloadedState, enhancer) {
  ...
  if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') {
      throw new Error('Expected the enhancer to be a function.')
    }

    return enhancer(createStore)(reducer, preloadedState)
  }
  ...
}


sagaMiddleware.run(rootSaga)

sagaMiddleware.run(rootSaga) 를 하면 runSaga() 가 호출된다. runSaga() 에서는 필요한 설정들에 대한 값들을 set 하고(sagaMonitor 같은) proc() 를 호출한다.(proc.js)
proc(env, iterator, context, effectId, getMetaInfo(saga), /* isRoot */ true, noop)

proc 에서 stdChannel 를 생성하는데, 이녀석은 약간의 경고문구만 추가한 eventChannel 이다. 이녀석은 createSagaMiddleware 를 할 때 생성된다.

proc 에서 next() 를 호출하고, next 는 iterator.next() 를 호출한다. iterator 를 이용해서 all() 로 묶인 task 들을 한번에 처리한다.


// saga.js
export default function* rootSaga() {
  yield all([
    helloSaga(),
    watchIncrementAsync()
  ])
}
...

// runSaga.js
function runSaga(_ref, saga) {
  ...

  var iterator = saga.apply(void 0, args);
  ...
  return immediately(function () {
    var task = proc(env, iterator, context, effectId, __chunk_1.getMetaInfo(saga),
    /* isRoot */
    true, __chunk_1.noop);

    if (sagaMonitor) {
      sagaMonitor.effectResolved(effectId, task);
    }

    return task;
  });
}

function proc(env, iterator, parentContext, parentEffectId, meta, isRoot, cont) {
  if (iterator[__chunk_1.asyncIteratorSymbol]) {
    throw new Error("redux-saga doesn't support async generators, please use only regular ones");
  }
  ...
  next(); // then return the task descriptor to the caller
  
  return task;
  /**
   * This is the generator driver
   * It's a recursive async/continuation function which calls itself
   * until the generator terminates or throws
   * @param {internal commands(TASK_CANCEL | TERMINATE) | any} arg - value, generator will be resumed with.
   * @param {boolean} isErr - the flag shows if effect finished with an error
   *
   * receives either (command | effect result, false) or (any thrown thing, true)
   */

  function next(arg, isErr) {
    try {
      var result;
      
      if (isErr) {
        result = iterator.throw(arg); // user handled the error, we can clear bookkept values
        clear();
      } else if (__chunk_1.shouldCancel(arg)) {
        /**
          getting TASK_CANCEL automatically cancels the main task
          We can get this value here
           - By cancelling the parent task manually
          - By joining a Cancelled task
        **/
        ...
      } else if (__chunk_1.shouldTerminate(arg)) {
        // We get TERMINATE flag, i.e. by taking from a channel that ended using `take` (and not `takem` used to trap End of channels)
        ...
      } else {
        result = iterator.next(arg);
      }

      ...
    } catch (error) {
      if (mainTask.status === CANCELLED) {
        throw error;
      }

      mainTask.status = ABORTED;
      mainTask.cont(error, true);
    }
  }
  ...
}


redux-saga 사용

redux-saga 는 특정 이벤트가 끝날때에 대한 작업을 미리 등록해 놓을 수 있다.(saga.js)

그래서 원래라면,

  1. 특정 이벤트가 발생하고, 
  2. 그 작업을 수행한 후(event handler)에 
  3. 우리가 원하는 작업을 실행하기 위해서 우리의 code를 추가해야 한다.(callback) 
하지만 redux-saga 는 그부분의 대한 구현에 관계없이 구현을 하면 된다.

redux-saga 를 이용해서 event 가 끝나는 시점에 호출되는 대신에,
  1. 먼저 특정 event 를 기다리는 code 를 실행하고 
  2. event 가 발생할 때 까지 control 을 다른 곳에 넘겨준다. 그리고 event 가 발생하면 다시 control 을 받아온다.(yield)

  yield takeEvery('INCREMENT_ASYNC', incrementAsync)

간단히 이야기 하면, await/async 를 사용하게 해주는 것이라 보면 된다.


아래 글을 읽으면 자세한 이야기를 알 수 있다.








[컴][js] redux-saga

redux-saga / redux saga / 사가 / 리덕스 사가


redux-saga vs await/async

"ref. 2 --> Why not use ES2016/Next async await?" 를 보면 saga 는 await/async keyword 를 대신해서 사용할 만한 방법인듯 싶다. 다만 이글에서도 언급하고 있지만, 글 자체가 최신의 redux-saga 의 내용이 아니기에 일단 정확한 확인은 필요하다.

await/async 보다 나은점

  1. test case 작성에 유리 : ref. 2 에 따르면 await/async 보다 유리한 점 하나는 request 를 시도할 때 await/async 는 직접적으로 request/fetch 함수를 호출하지만, redux-saga 에서는 call 을 호출하고 관련 parameter 만 전달하기 때문에, test case 를 만들 때 그 함수를 mock up 으로 대체할 수 있다 정도인 듯 싶다.
  2. browser 지원 : 브라우저에서 아직 await/async 를 공식적으로 지원하지 않는다. redux-saga 에서 사용하는 generator 에도 문제는 있다. "generator 에 대한 browser 의 지원"도 모든 브라우저에서 완벽하게 지원하지는 않는다.(적어도 IE 는 전혀 지원하지 않는다.) 그래서 이 경우에는 polyfill 을 따로 다운로드 해야 하는데, 그 파일의 용량이 크다.[ref. 3]

Redux Saga chooses generators over async/await

  • https://redux-saga.js.org/ --> Redux Saga chooses generators over async/await
  • redux-saga 가 async/await 를 안쓰고 generator 를 쓰는 이유를 알려준다. 
    • 스케쥴링의 간단함(scheduling simplicity)과 Promise를 사용하는 현재 Saga 개념들의 semantics 를 유지하기가 어려워진다.
    • async/await 은 간단하게 cacellation 같은 것들이 불가능하다.
    • generator 를 쓰면, effect 들이 언제, 어떻게 실행되는지를 통제할 수 있다.

redux-saga examples

Saga

saga 에 대한 설명은 See Also 2, 3 에서 확인을 하자. 

대체로 saga 는 분산시스템에서 쓰이는 transaction 방식을 이야기 하는 듯 하다. 

만약 분산시스템에서 여러개 transaction 를 수행한다고 하면, 이 여러개의 transaction 이 crash 가 났을 때 roll back 하는 법이 (compensate) 있는 것이다. 그래서 만약 1번째 transaction 부터 3번째 transaction 까지는 잘 동작하고, 4번째 transaction 에서 crash 가 나면, 다시 3번째 transaction 이 완료된 시점으로 가서, 다시 4번째 transaction 을 실행하는 것이다.[see also 3]

See Also

  1. Read Me · Redux-Saga
  2. Confusion about Saga pattern - Roman Liutikov - Medium : saga 에 대한 설명
  3. Clarifying the Saga pattern  by kellabyte, 2012-05-30 : saga 에 대한 설명

References

  1. 5. External Resources · Redux-Saga: 여러가지 Redux-Saga 와 관련된 글들을 확인할 수 있다.
  2. Handling async in Redux with Sagas, 2016-01-23
  3. Master Complex Redux Workflows with Sagas, 2016-02-02

[컴] 안드로이드 폰 광고ID 재설정

galaxy / android / 안드로이드 폰 광고 아이디 설정


광고 ID 재설정

  • 갤럭시 기준
  • 설정 --> Google --> 광고 --> 광고 ID 재설정



광고 ID 재설정이 필요한 이유

이 ID 가 웹등에 접속할 때 내가 누군인지를 구분해주는 역할을 하게 된다. 그렇기에 주기적으로 이것을 변경해주면, 어느정도의 익명성이 보장될 수 있다.(하지만 당연하게도, 완벽한 익명성을 보장하진 않는다.)



[컴][js] OpenSSL hmac in NodeJS

노드에서 hmac 사용 / open ssl / openssl hmac 사용법 / 노드제이에스 / node js

OpenSSL hmac in NodeJS


ref. 1 과 ref. 2 를 참고하면 대략적으로 사용법을 알 수 있다.

const crypto = require('crypto');
const hmac = crypto.createHmac('sha256', 'a secret');

hmac.update('some data to hash');
console.log(hmac.digest('hex'));


References

  1. crypto.createHmac
  2. crypto_class_hmac


[컴][js] AdoniJS 에서 큰 DB data 를 retrieve 할 때

chunk / big data / adonijs lucid big data / database cursor / adonis 에서 cursor 사용방법 / db query / size / big size / 쿼리 / node query / how to handle big query result


AdonisJS Lucid 에서 stream 사용

AdonisJS-lucid 의 사용에서 큰 결과 값을 갖는 query 를 다룰 때 stream 을 사용하면 된다. 일반적인 cursor 의 사용이라고 생각하면 될 듯 하다.

AdonisJS Lucid 의 문서에서는 찾기가 힘들었는데, community 에서 자료를 찾았다.(ref. 1)

내용을 정리하면, Lucid는 KnexJS 를 사용하고 있고, KnexJS 에서 streams 를 제공한다. 그것의 사용법은 아래와 같다.

아래 stream 의 예제는 ref. 1 을 참고해서 만들었다.

const ccursor =
  Database.connection('cocktail')
    .raw(
      `SELECT * FROM point AS t1 WHERE t1.id !='tester'
      ORDER BY id ASC`
    ).stream()
  
const prom = new Promise((resolve, reject)=>{
    Logger.error('test2');
    ccursor.on('data', (row)=>{
      Logger.info('test-data');
    })
    ccursor.on('end', () => {
      Logger.info('test-data');
      resolve()
    })
    ccursor.on('error', (param1) => {
      Logger.error('test-error : ' + param1);
      reject()
    })
});
await prom.then((val)=>{
    Logger.info('test-resolve');
}).catch((val)=>{
    Logger.error('test-reject');
});


위의 code 를 실행하면 Logger.info('test-data'); 가 계속 실행되다가 끝날때 Logger.info('test-data'); 가 실행될 것이다.

또는 아래처럼 작성해도 된다.
const prom = new Promise((resolve, reject)=>{
  const ccursor =
    Database.connection('cocktail')
      .raw(
        `SELECT * FROM g5_point AS t1 WHERE t1.mb_id !='admin'
        ORDER BY po_id ASC`
      ).stream();
  ccursor.on('data', (row)=>{
    Logger.error('test-data');
  })
  ccursor.on('end', () => {
    Logger.error('test-end');
    resolve()
  })
  ccursor.on('error', (param1) => {
    Logger.error('test-error : ' + param1);
    reject()
  })
});
await prom.then((val)=>{
  Logger.error('test-resolve');
}).catch((val)=>{
  Logger.error('test-reject');
});


async

아래처럼 async 를 사용할 수 있다.

const prom = new Promise(async (resolve, reject)=>{
  const ccursor =
    Database.connection('cocktail')
      .raw(
        `SELECT * FROM g5_point AS t1 WHERE t1.mb_id !='admin'
        ORDER BY po_id ASC`
      ).stream();
  ccursor.on('data', async (row)=>{
    Logger.error('test-data');
  })
  ccursor.on('end', async () => {
    Logger.error('test-end');
    resolve()
  })
  ccursor.on('error', async (param1) => {
    Logger.error('test-error : ' + param1);
    reject()
  })
});
await prom.then((val)=>{
  Logger.error('test-resolve');
}).catch((val)=>{
  Logger.error('test-reject');
});



아직은 사용하기가 쉽지 않다. 그리고 await/async 와 잘 맞지 않아 보인다. 그래서 자꾸 memory 가 넘치는 듯 하다.

개인적으로 현재로서는 그냥 offset, limit 을 이용해서 일정부분만 retrieve 해서 가져와서 사용하고, 다시 또 일정부분을 가져와서 사용하는 방식이 더 나아 보인다.



See Also

  1. laravel 에서 큰 Database result 를 처리할 때

References

  1. A Way to process large results of database Query in Adonis without consuming too much RAM - Help / Database - Forum - AdonisJS Framework
  2. Knex.js - A SQL Query Builder for Javascript