かもメモ

自分の落ちた落とし穴に何度も落ちる人のメモ帳

Googleスプレッドシート 時間を数値にして計算したい。

勤怠表から時給換算とか、01:30:00な経過時間フォーマットな表のデータを1.5とかに置き換えたい時

VALUE を使う

VALUE
Google スプレッドシートで認識される日付、時刻、番号の書式の文字列を数値に変換します。
VALUE("12:00:00")
出典: VALUE - ドキュメント エディタ ヘルプ

VALUEで時間単位に変換する場合は、

=VALUE( A1 ) * 24

と24をかけてやればOK。
分換算なら=VALUE( A1 ) * 24 * 60です。

経過時間 時間換算 分換算
1:00:00 1 60
1:30:00 1.5 90
1:45:00 1.75 105

TIMEVALUEだと24時間超えてる値だとうまくいかない

似た関数にTIMEVALUEというものがありました。
名前的にもこちらの方が時間系の変換に良さそうなのですが

TIMEVALUE
1 日 24 時間のうちの、時間が表す割合を返します。
出典: TIMEVALUE - ドキュメント エディタ ヘルプ

とあるように、24時間区切りなので、作業工数や勤務時間の合計とかで値が24時間を超えていると、意図しないデータ (時間が減る)になってしまうので注意が必要です。

TIMEVALUEでの時間換算式

=TIMEVALUE( A1 ) * 24

👇

経過時間 時間換算 (VALUE) 時間換算 (TIMEVALUE)
1:00:00 1 1
1:30:00 1.5 1.5
1:45:00 1.75 1.75
24:00:00 24 0
25:00:00 25 1
28:30:00 28.5 4.5

と、24時間以内ならTIMEVALUEでもうまくいってしまうので、注意が必要です。
スプレッドシートで経過時間から計算を行う場合はVALUEを使いましょう。
というお話でした。


JavaScript (ES2015) 今更のPromise (3) 非同期処理を順番に実行するチョットマニアックなメモ

前回までのあらすじ

非同期処理を平行に実行する場合はPromise.allPromise.raceを使えばよいのですが、非同期処理で受け取った値を使って次の処理を行いたいなど順番(同期的)に実行したい場合のメモ

Promise~thenでのメソッドチェーン

たぶん一番正当なやり方。
Promiseオブジェクトを返す関数を実行してresolveされた時に実行されるthenメソッド内で、Promiseオブジェクトを返す関数をreturnすることで、Promise.prototype.thenのメソッドチェーンができる。
エラーになるとcatchに処理が流れる。

実行される関数

function myFunc(a, b = 0) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if(a) {
        let res = (a + b) * 2
        console.log(`(${a} + ${b}) * 2 = ${res}`)
        resolve( res )
      } else {
        reject(`Error a is ${a}`)
      }
    }, 10)
  })
}

function onReject(error) {
  console.log('>> onReject', error)
}

Promise.prototype.thenのメソッドチェーン

myFunc(1).then((res1) => {
  // res1 => 2
  return myFunc(2, res1)
})
.then((res2) => {
  // res2 => 8
  return myFunc(3, res2)
})
.then((res3) => {
  // res3 => 22
  console.log('> Promaise Complete', res3)
})
.catch(onReject)
// => (1 + 0) * 2 = 2
// => (2 + 2) * 2 = 8
// => (3 + 8) * 2 = 22
// => > Promaise Complete 22

エラーになる場合

myFunc(1).then((res1) => {
  // res1 => 2
  return myFunc(0, res1) // => reject になる
})
.then((res2) => {
  return myFunc(3, res2)
})
.then((res3) => {
  console.log('> Promaise Complete', res3)
})
.catch(onReject)
// => (1 + 0) * 2 = 2
// => >> onReject error a is 0

エラーが発生するとそれ以降のthenはスキップされる。
わかりやすいですね!

promise.then(successCallback, failureCallback) の場合

Promiseを使う - JavaScript | MDNに書かれているpromise.then(successCallback, failureCallback)で同期的に実行させようとする場合。

myFunc(1).then((res1) => {
  myFunc(2, res1).then((res2) => {
    myFunc(3, res2).then((res3) => {
      console.log('> Promaise Complete', res3)
    }, (error) => console.log('Error 3', error))
  }, (error) => console.log('Error 2', error))
}, (error) => console.log('Error 1', error))
// => (1 + 0) * 2 = 2
// => (2 + 2) * 2 = 8
// => (3 + 8) * 2 = 22
// => > Promaise Complete 22

// Errorになる場合
myFunc(1).then((res1) => {
  myFunc(0, res1).then((res2) => {
    myFunc(3, res2).then((res3) => {
      console.log('> Promaise Complete', res3)
    }, (error) => console.log('Error 3', error))
  }, (error) => console.log('Error 2', error))
}, (error) => console.log('Error 1', error))
// => (1 + 0) * 2 = 2
// => Error 2 Error a is 0

エラーの場合は先のthenをメソッドチェーンした場合と同じで、エラーになるとそれ以降の処理には流れず、エラーの時点で処理は完了になります。

thenの第一引数内で次のPromiseオブジェクトを返す関数を実行する必要があるので、同期処理が多くなるとコードのようにネストして見通しがあまり良くない感じです。ただ、エラーの処理をそれぞれ別に設定できるのはメリットかも知れません。

promise.then(successCallback, failureCallback) でthenのメソッドチェーンにした場合

ここからチョットマニアックかも。

myFunc(1)
.then(
  (res1) => myFunc(0, res1),
  (error) => console.log('Error 1', error)
)
.then(
  (res2) => myFunc(3, res2),
  (error) => console.log('Error 2', error)
)
.then(
  (res3) => console.log('> Promaise Complete', res3),
  (error) => console.log('Error 3', error)
)
.catch(onReject)
// => (1 + 0) * 2 = 2
// => Error 2 Error a is 0
// => > Promaise Complete undefined

上の例では、

  1. 1つめのthen: successCallback => muFunc(0, res1) (res1は2)
  2. 2つめのthen: failureCallback => console.log('Error 2', error)
  3. 3つめのthen: successCallback => console.log('> Promaise Complete', res3) (res3はundefined)

thenの第二引数にfailureCallbackが渡されていると、エラーの時にcatchに流れるのではなく、failureCallbackが呼ばれ、メソッドチェーンしてる次のthenに処理が流れるようです。

次のthenに流れた時に渡される引数について

先の例ではエラーになった時のコールバックではreturn console.log('Error 2')が実行されundefinedが返されているので、エラーで次のthenに渡される引数が「常にundefined」なのか「コールバック関数で返された値」がなのかが判断しきれません。

そこで、明示的に値を返して試してみます。

myFunc(0)
.then(
  (res1) => myFunc(2, res1),
  (error) => {
    console.log('Error 1')
    return 'Foo'
})
.then(
  (res2) => console.log('> Promise Complete:', res2),
  (error) => console.log('Error2', error)
)
// => Error 1
// => > Promise Complete: Foo

どうやら、エラーコールバックで返される(returnされる)値が次のthen内のsuccessCallbackに渡され実行されるようです。

Promise.reject()を返せば次のthenはエラーコールバックの方が呼ばれる

明示的にreturn Promise.reject()すると、次のthenの処理はエラーコールバックfailureCallbackの方に処理が流れました。

myFunc(0).then(
  (res1) => myFunc(2, res1),
  (error) => {
    console.log('Error 1')
    // Promise.reject を返す
    return Promise.reject('Reject 1')
})
.then(
  (res2) => console.log('> Promise Complete:', res2),
  (error) => console.log('Error2', error)
)
// => Error 1
// => Error2 Reject 1

 
promise.then(successCallback, failureCallback).then(...)の形式でメソッドチェーンをした場合は、

  • エラーの場合はcatchではなく、第二引数のfailureCallbackが実行される
  • 先の処理の成功失敗に関わらず、次のthenが呼ばれる
  • 次のthenのコールバックに渡される値は、先のコールバック内でreturnされた値
  • 次のthenでエラーに処理を流したい場合は、return Promise.reject("エラーコールバックに渡す引数")とする

という感じっぽいです。

先の非同期処理が成功しようが失敗しようが処理を待って次の処理を実行することができるので、値があれば値を使って、無ければ無いなりで必須な共通処理とか使い方はあるかもしれません。

myFunc(0)
.then(
  (res1) => {
    console.log('Success 1: res1 is ', res1)
    return myFunc(res1)
  },
  (error1) => {
    console.log('Error 1: default val 1')
    return 1
  }
)
.then(
  (res2) => {
    // 前のthenでPromise.rejectされないので、必ずココが実行される
    console.log('Common callback: res2 is', res2)
    return myFunc(res2)
  },
  (error2) => {
    console.log('Error 2:', error2)
    return Promise.reject('ERROR 2')
  }
)
.then(
  (res3) => {
    console.log('Promise complete:', res3)
  },
  (error) => {
    console.log('Error3', error)
  }
)
// => Error 1: default val 1
// => Common callback: res2 is 1
// => (1 + 0) * 2 = 2
// => Promise complete: 2

途中からthen~catchのメソッドチェーンにした場合

TASK

function myFunc2(a, b = 0) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if(!a) {
        reject(`Error a is ${a}`)
      }
      let res = (a + b) * 2
      console.log(`(${a} + ${b}) * 2 = ${res}`)
      resolve( res )
    }, 10)
  })
}

1. 成功した場合は、そのまま次のthenが実行される

myFunc2(1)
.then(
  (res1) => {
    console.log('Success 1: res1 is ', res1)
    return myFunc(2, res1)
  },
  (error1) => {
    console.log('Error 1')
    return 2
  }
)
.then(
  (res2) => {
    console.log('Success 2: res2 is ', res2)
  }
)
.catch(
  (error) => {
    console.log("Error catch", error)
  }
)
// => (1 + 0) * 2 = 2
// => Success 1: res1 is  2
// => (2 + 2) * 2 = 8
// => Success 2: res1 is  8

2. 失敗し、コールバック関数内で単に値をreturnした場合も、そのまま次のthenが実行される

myFunc2(0)
.then(
  (res1) => {
    console.log('Success 1: res1 is ', res1)
    return myFunc(2, res1)
  },
  (error1) => {
    console.log('Error 1')
    return 2
  }
)
.then(
  (res2) => {
    console.log('Success 2: res2 is ', res2)
  }
)
.catch(
  (error) => {
    console.log("Error catch", error)
  }
)
// => (0 + 0) * 2 = 0
// => Error 1
// => Success 2: res2 is  1 ... 先のエラー関数でreturnされた値が渡される

3. 失敗し、コールバック関数内でPromise.reject()を返した場合、catchに処理が流れる

myFunc2(0)
.then(
  (res1) => {
    console.log('Success 1: res1 is ', res1)
    return myFunc(2, res1)
  },
  (error1) => {
    console.log('Error 1')
    return Promise.reject("Reject!")
  }
)
.then(
  (res2) => {
    console.log('Success 2: res2 is ', res2)
  }
)
.catch(
  (error) => {
    console.log("Error catch", error)
  }
)
// => (0 + 0) * 2 = 0
// => Error 1
// => Error catch Reject! ... 先のコールバック関数で呼ばれたPromise.rejectに渡された値が引数で渡される

4. 成功したコールバック関数内でPromise.rejectが返された場合もcatchに処理が流れる

myFunc2(1)
.then(
  (res1) => {
    console.log('Success 1: res1 is ', res1)
    // 成功したけど、reject
    return Promise.reject(`res 1 is ${res1}`)
  },
  (error1) => {
    console.log('Error 1')
    return Promise.reject("Reject!")
  }
)
.then(
  (res2) => {
    console.log('Success 2: res2 is ', res2)
  }
)
.catch(
  (error) => {
    console.log("Error catch", error)
  }
)
// => (1 + 0) * 2 = 2
// => Success 1: res1 is  2
// => Error catch res 1 is 2

途中からthen~catchのメソッドチェーンを繋ぐことは問題ないようなので、successCallback, failureCallbackを使った場合の特性を知っていれば、特定のエラー処理だけ分けたいとかって場合などに使えるかもです。(あまりお行儀はよくなさそうだけど...)
 
さっさとawait /asyncに行けばいいのに色々と気になって試してしまいました。(学習が遅い言い訳


これから学ぶ JavaScript

これから学ぶ JavaScript

JavaScript (ES2015) 今更のPromise (2) Promiseが返された後の処理のメモ

前回のあらすじ

通常のPromiseを返す関数で明示的にresolve()reject()returnしてないと関数の終わりまで実行されるのでは?と思い調べてみました。

明示的に resolve/reject がreturnされていない場合

function myFunc(a, b = 0) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if(!a) {
        reject( `Error a is ${a}` )
      }
      let res = (a + b) * 2
      console.log(`(${a} + ${b}) * 2 = ${res}`)
      resolve( res )
    }, 10)
  })
}

myFunc(0)
.then((res1) => console.log(res1))
.then((res2) => console.log('CHAIN!!', res2))
.catch((error) => console.log(error))
// => (0 + 0) * 2 = 0
// => Error a is 0

やはりrejectが呼ばれた後も、関数の中は最後まで実行されていました。
恐らくresolve( res )も実行されているけど、元の呼び出し元では先にrejectが返っているので、再度thenに処理が流れたり、次のthenが呼び出されることもなく無視するようになってるっぽい。
ただresolve, rejectしてもその後の処理が無駄に実行されるので、Promiseオブジェクトを返す関数内ではそれ以上処理が必要ないならreturn resolve()return reject()とした方が良さそう。

並列処理 Promise.all, Promise.race は処理が返っても残りの処理は実行され続けている。

Promise.allでエラーが返ったり、Promise.raceで最初の値が返されても、呼び出していた処理は引き続き実行されているようです。
task

const task1 = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('> TASK-1')
      resolve('task1')
    }, 20)
  })
}

const task2 = () => {
  return new Promise((resolve, rejecy) => {
    setTimeout(() => {
      console.log('> TASK-2')
      resolve('task2')
    }, 5)
  })
}

const task3 = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('> TASK-3')
      reject('REJECT TASK3')
    }, 10)
  })
}

Promise.all

Promise.all([task1(), task2(), task3()])
.then((res) => {
  console.log('>>> Promise.all', res)
})
.catch((error) => {
  console.log('>>> Promise.all ERROR')
})
// >> TASK-2
// >> TASK-3
// >>> Promise.all ERROR
// >> TASK-1

task3rejectされcatchに処理が流れているが、呼び出されたtask1はそのまま実行されている

Promise.race

Promise.race([task1(), task2(), task3()])
.then((res) => {
  console.log('>>> Promise.race', res)
})
.catch((error) => {
  console.log('>>> Promise.race ERROR')
})
// >> TASK-2
// >>> Promise.race task2
// >> TASK-3
// >> TASK-1

task2の処理が完了しても呼び出されていたtask1, task3は引き続き実行はされている

APIとかでエラーがあればエラーを返して処理をまるっと終了させたいような場合はprocess.exit(1)とかでプロセスを終了させてしまうのが良いのかな? (API作ったこと無いから正解かどうか判断しかねるけど)