初めに
前回の続きです。
Memo
Actions/States of Promise:
(Unsettled)Pending⇒neither onFulfilled or onRejected
(return resolve()⇒Settled)Fulfilled⇒onFulfilled(.then(onFulfilled))
(return reject()/unexpected⇒Settled)Rejected⇒onRejected(.then(..., onRejected)/.catch())
async function
やっとですね。asyncとawaitのおかげで画期的な展開が迎えるようになりました。
Thunkやcoの理解は一苦労してたけれど、coの使い方では幅広いデータタイプでもとても快く対応してくれています。しかし依然として改善される余地があれば向上が止まらないのです。
coはyieldにThunkかPromiseしか受け入れられないことに対し、
asyncはawaitに解決されたPromiseか拒否されたPromise、あるいは Promiseではない値なら暗黙にPromiseにしてから別のタスクキューに移させることによって非同期処理を実現します。
下は参考文章から取ったデモコードです。(一部練習のため変更あり)
// example1
async function getStockPriceByName(name) {
// function getStockName(str) {...}
// function getStockPrice(stockName) {...}
const stock = await getStockName(name);
const stockPrice = await getStockPrice(stock);
return stockPrice;
}
getStockPriceByName('goog').then((result) => {
console.log(result);
});
// example2
function timeout(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
async function waitToPrint(value, ms) {
await timeout(ms);
console.log(value);
}
waitToPrint('hello world', 1000);
// or
async function timeout(ms) {
await new Promise((resolve) => {
setTimeout(resolve, ms); // after "ms" seconds, execute resolve()
});
}
async function waitToPrint(value, ms) {
await timeout(ms);
console.log(value);
}
// example3 - async function always returns promise
async function foo() {
return 'abc'
}
foo().then((result) => {
console.log(result);
});
// abc
// example4 - Error handling
async function foo() {
throw new TypeError('Typo');
}
foo().then(
result => console.log('resolve', result),
error => { if (error instanceof TypeError) console.log('reject', error) }
)
下は書き方のデモコードです。
// usage
// function declaration
async function foo() { }
// function expression
const foo = async function () { };
// arrow function
const foo = async () => { };
// object method
let obj = {
async foo() {
// something
}
};
obj.foo().then();
// class method
class Storage {
constructor() {
// CacheStorage.open()
this.cachePromise = caches.open('avatars');
}
async getAvatar(name) {
const cache = await this.cachePromise;
return cache.match(`/avatars/${name}.jpg`);
}
}
const storage = new Storage();
storage.getAvatar('jude').then();
async function with then()
async関数では内部コードすべての実行が完了し、返り値が解決されたPromiseか拒否されたPromiseだと約束されている。
// demo
async function getTitle(url) {
// get the response
let response = await fetch(url);
// wait until text is ready
let html = await response.text();
// return html title
// return html.match(/<title>([\s\S]+)<\/title>/i)[1]; // the result is unexpected
return html.match(/<title>([^<]+)<\/title>/i)[1];
}
getTitle('https://tc39.github.io/ecma262/').then(
result => console.log(result),
err => console.log(err)
);
// ECMAScript® 2023 Language Specification
// debugger
// this is the result when i use ".match(/<title>([\s\S]+)<\/title>/i)[1]"
// let str = `<title>ECMAScript® 2023 Language Specification</title><script src="ecmarkup.js?cac
// he=YeiTF_tp" defer=""></script></head><body><div id="shortcuts-help">
// <ul>
// <li><span>Toggle shortcuts help</span><code>?</code></li>
// <li><span>Toggle "can call user code" annotations</span><code>u</code></li>
// <li><span>Navigate to/from multipage</span><code>m</code></li>
// <li><span>Jump to search box</span><code>/</code></li>
// </ul></div><div id="menu-toggle"><svg xmlns="http://www.w3.org/2000/svg" style="
// width:100%; height:100%; stroke:currentColor" viewBox="0 0 120 120">
// <title>Menu`
// console.log(str.match(/<title>([\s\S]+)<\/title>/i)[1]);
// ECMAScript® 2023 Language Specification
// note: in nodejs, it does work very well, maybe the problem occurred in fetch?
await
普通の関数でもPromiseを返してからthen()でコールバックを呼び出すことができますがawaitはasync関数以外は作用しない。普通の関数ではawaitを通してresolveされたPromiseを待ったり、あるいはrejectされたPromiseのエラー処理したりするのができません。
一つ誤解されやすいところだと思いますが。
The expression is resolved in the same way as Promise.resolve(), which means thenable objects are supported, and if expression is not a promise, it's implicitly wrapped in a Promise and then resolved.
それはawaitを使えばPromiseでない値をPromiseオブジェクトに変換してくれるところです。
確かにthenableオブジェクトではカスタムのthen((resolve, reject))メソッドを利用すれば解決されたPromiseとして返し、それからも.then()や.catch()でPromiseチェイン続くことができる。
しかしawait本当の作用は、Promiseでない値をPromiseオブジェクトに包んでMicrotaskへ移行して非同期処理を行わせ、返り値を待つことです。詳しくは thenable vs. Promise (Microtask) のコードや解釈を参考にしていただければ幸いです。
// is Promise
async function isPromise1() {
let data = new Promise((resolve, reject) => {
resolve('abc');
});
console.log(data instanceof Promise);
data.then((result) => console.log(result));
}
// is Promise
async function isPromise2() {
let data = Promise.resolve('abc');
console.log(data instanceof Promise);
data.then((result) => console.log(result));
}
// not Promise
async function notPromise1() {
let data = await new Promise((resolve, reject) => {
resolve('abc');
});
console.log(data instanceof Promise);
// data.then((result) => console.log(result));
// TypeError: data.then is not a function
}
// not Promise
async function notPromise2() {
let data = await Promise.resolve('abc');
console.log(data instanceof Promise);
// data.then((result) => console.log(result));
// TypeError: data.then is not a function
}
isPromise1(); // true
isPromise2(); // true
notPromise1(); // false
notPromise2(); // false
awaitから値がアサインされた変数はMicrotask Queueから戻った値であり、Promiseインスタンスではないためthen()メソッドもとうぜん使えなくなる。
変数ではなく、awaitへアサインする前にPromiseに使いましょう!
// use then() after Promise, not variable
async function awaitPromiseChain() {
let data = await Promise.resolve('abc')
.then((result) => console.log(result));
}
awaitPromiseChain(); // abc
await with reject (error handling)
async関数実行中、awaitがrejectされたPromiseを受けるとすぐasync関数を中断する。
// await with reject
async function foo() {
await Promise.reject('something wrong'); // reject will suspend following execution
console.log('not work');
}
async function foo1() {
return 'it works';
}
foo().then((result) => console.log(result));
foo1().then((result) => console.log(result));
console.log('1');
// 1
// it works
// UnhandledPromiseRejection
確かにasync関数は、普通の関数のように中断されるとキャッチされていないエラーがそのまま外側に投げプログラムが中断されることがないです。それにほかのasync関数がいてもエラーに影響されず、未処理のエラーが最後一気に投げてくるだけです。
それでもエラーキャッチや処理が大事です。async関数内では一つのawaitに限られていなく、多くの場合はステップバイステップのように前の処理が確実に完成したうえ、それからの操作を実行していく、一種の確認作業でもあります。
外側からエラーをキャッチするのがいいけれどあまり意味しません。(上のようにasync関数はメインをブロッキングしない。)
なのでasync関数内try...catchでawaitを包んだり、awaitにアサインする値がPromiseオブジェクトなら.catch()でキャッチしてもいいです。
// error handling
async function foo() {
await Promise.reject('something wrong'); // reject will suspend following execution
console.log('not work');
}
foo()
.then((result) => console.log(result))
.catch((err) => console.log(err));
// something wrong
// better way
async function foo() {
// to prevent suspend, catch the error in function first
try {
await Promise.reject('something wrong');
} catch (err) {
console.log(err);
return await Promise.resolve('hello world');
}
}
foo()
.then((result) => console.log(result));
// something wrong
// hello world
// or
async function foo() {
await Promise.reject('something wrong')
.catch((err) => console.log(err));
return await Promise.resolve('hello world');
}
foo()
.then((result) => console.log(result));
// something wrong
// hello world
try...catchには実行してほしいコード、もし予定通りに実行してなかったりエラーだったり場合はキャッチして出力。
下のようにtry...catchのawaitにエラーになってもreturnに置かれたawaitは実行してくれます。
// use try...catch
async function foo() {
// catch error, and return resolved Promise
try {
await new Promise((resolve, reject) => {
throw new Error('something wrong');
});
} catch (err) {
console.log(err);
}
// return 'hello world';
return await ('hello world'); // await has no effect in here
}
foo()
.then((result) => console.log(result))
.catch((err) => console.log(err));
// note: double check, if return value was reject, we can catch error here
// Error: something wrong
// hello world
外側に.catch()を使うことで、もし返り値がエラーかrejectされたPromiseだった場合はキャッチしてくれます。
// multiple actions
async function multipleAwait() {
try {
const value1 = await firstStep();
const value2 = await secondStep(value1);
const value3 = await thirdStep(value1, value2);
console.log('Final: ', value3);
} catch (err) {
console.log(err);
}
}
複数のawaitからエラーによって特定する場合は、エラーメッセージから分類してからどう処理していくかを決めるのもいいと思います。
// retry with for Loop
const superagent = require('superagent');
const retryCounter = 3;
async function test() {
let i;
for (i = 0; i < retryCounter; ++i) {
try {
await superagent.get('http://google.com/this-throws-an-error');
break;
} catch (err) { }
}
console.log(i);
}
test(); // 3
thenable vs. Promise (Microtask)
thenableオブジェクトというのはthen()メソッド持ちのオブジェクトのことです。下は同じく参考文章から取った例です。
// await with thenable
class SleepTime {
constructor(timeout) {
this.timeout = timeout;
}
then(resolve, reject) {
const startTime = Date.now();
setTimeout(
() => resolve(Date.now() - startTime),
this.timeout
);
}
}
(async () => {
const sleepTime = await new SleepTime(999);
console.log(sleepTime);
})();
function sleep(interval) {
return new Promise((resolve) => {
setTimeout(
() => resolve(),
interval);
})
}
async function oneToFiveInAsync() {
for (let i = 1; i <= 5; i++) {
console.log(i);
await sleep(1000);
}
}
oneToFiveInAsync();
// 1
// 2
// 1007
// 3
// 4
// 5
出力結果にとても気になりますね。一番目のasync関数はIIFEなのになぜ二番目のasync関数oneToFiveInAsync()より遅くなるでしょうか。
前も少し触れていたが、非同期関数Promiseや非同期APIら(setTimeout/setIntervalなど)は別々のキューに優先順で実行されるのです。
PromiseはMicrotaskに、setTimeout/setIntervalはMacrotaskにタスクを実行する。実行が終わったら各自のQueue(キュー、隊列)に送られ、コールバックの実行を待ちます。
優先順としてはMicrotask Queueは常にMacrotask Queueより先、Macrotask Queueの実行がMicrotask Queueがクリアするまでしない。そしてMicrotask QueueはCall stackがクリアするまで実行しない。
Call stack > Microtask Queue > Macrotask Queue
Microtask Queueは解決されたPromiseが順番通りに実行する隊列ですが、
Macrotask Queueは多くはタイマー持ち非同期APIのコールバック隊列なので、順序には意味を持たない。
ここから上の例の実行順の説明です。
自分の解釈が正しいかはかわかりませんが、説明に試してみたいと思います。
(taskには番号づけて説明するが、task Queueは当時の実行順で書いています。)
Call stack:
IIFE⇒new SleepTime(1000)⇒setTimeout()⇒
Macrotask (1) start, timer in IIEF is executing
(Is Call stack clear? No.
Is Microtask Queue clear? Yes.
Is Macrotask Queue clear? Yes.)
↓
Call stack:
oneToFiveInAsync()⇒for Loop
(Is Call stack clear? No.
Is Microtask Queue clear? Yes.
Is Macrotask Queue clear? Yes.)
↓
Call stack:
for Loop (1/5) start⇒console.log(1)⇒sleep(1000)⇒
new Promise() in sleep function⇒ Microtask (1) start ⇒
setTimeout()⇒Macrotask (2) start, timer in Promise is executing
(Is Call stack clear? Yes.
Is Microtask Queue clear? Yes.
Is Macrotask Queue clear? Yes. )
↓ one second later
Macrotask (1) end, timer in IIEF had been executed⇒Macrotask Queue (1)
Macrotask (2) end, timer in Promise had been executed⇒Macrotask Queue (2)
(Is Call stack clear? Yes.
Is Microtask Queue clear? Yes.
Is Macrotask Queue clear? No. )
↓
Macrotask Queue (1) start, setTimeout callback anonymous executed⇒
resolve(Date.now() - startTime)⇒ Microtask (2) start
(Is Call stack clear? Yes.
Is Microtask Queue clear? Yes.
Is Macrotask Queue clear? No. )
↓
Macrotask Queue (2) start, setTimeout callback anonymous executed⇒
resolve()⇒ Microtask (1) end ⇒
return resolved Promise⇒ Microtask Queue (1)
(Is Call stack clear? Yes.
Is Microtask Queue clear? No.
Is Macrotask Queue clear? Yes. )
↓
Microtask Queue (1) start & end ⇒await sleep(1000)⇒for Loop (1/5) done
(Asynchronous) Microtask (2) end ⇒ Microtask Queue (1)
(Is Call stack clear? No.
Is Microtask Queue clear? Yes.
Is Macrotask Queue clear? Yes. )
↓ for loop is synchronous
Call stack:
for Loop (2/5) start
(Is Call stack clear? No.
Is Microtask Queue clear? No.
Is Macrotask Queue clear? Yes. )
↓
console.log(2)⇒sleep(1000)⇒
new Promise() in sleep function⇒Microtask (3) start⇒
setTimeout()⇒Macrotask (3) start, timer in Promise is executing
↓
(Is Call stack clear? Yes.
Is Microtask Queue clear? No.
Is Macrotask Queue clear? Yes. )
Microtask Queue (1) start & end ⇒
sleepTime = await new SleepTime(1000)⇒console.log(sleepTime)
↓
(Is Call stack clear? Yes.
Is Microtask Queue clear? Yes.
Is Macrotask Queue clear? Yes. )
↓ one second later
Macrotask (3) end, timer in Promise had been executed⇒Macrotask Queue (1)
...
...とかなり自分勝手に解釈して書いてみました。
長くて読みづらいかもしれないが、ほかのスレッド/タスクキューとの相互作用と、レンダリングのタイミングや、非同期HTTPリクエストなども加えればもっと複雑になります。今の自分はとにかくこの三つの実行順に理解したいと思います。
同じ例で少し変更すれば全然違う結果が出てきました。
(async () => {
const sleepTime = await new SleepTime(0);
console.log(sleepTime);
})();
...
// 1
// 5 // sleepTime
// 2
// 3
// 4
// 5
出力順番が変わったというのは、new SleepTime(0)がsleep(1000)より先にタイマーが終わりコールバックからresolve()によって解決されたPromiseがMicrotask Queue隊列に入ってしまったわけです。
その理由を説明する前にまずthenable中のsetTimeout()とPromise中のsetTimeout()の動きを解釈してみたいと思います。
thenableのnew SleepTime()はsetTimeout(() => resolve(), ms)、タイマーの後anonymous()⇒resolve()によりMicrotask (1)が始まり、そして解決されたPromiseをMicrotask Queueへ送る。
一方sleep(1000)はnew Promise()実行の時点でMicrotask (2)が始まり、そのあとタイマーからanonymous()⇒resolve()の実行により解決されたPromiseをMicrotask Queueへ送るのです。
これは一番目の例でも二番目の例でも同じタスク順に沿って動作するのです。
もう気づいているかもしれませんが、Microtask Queueの順番はMicrotaskのタスク順とは関係なく、先に処理が終わる方がMicrotask Queueに入り、そのあとcall stackへ移行し実行するのがMicrotask Queueの順番から一番古いやつを取り出し実行していくのです。
なおこの例ではnew SleepTime(0)からnew SleepTime(995)まで順番が変わらずsleepTimeがさきに出力されるのです。new SleepTime(999)なら実行速度により順番が上がったり下がったりしています。
この二つの例からCall stack、Microtask Queue、Macrotask Queueの検証によりタイマーが狂う理由、そして Promiseの非同期操作というのはMicrotaskにあること 、今回ではうまく説明できたらいいと思います。
ここまで来て少し変な言い方がするかもしれませんが、こんな検証や説明で自分の理解が本当に正しいのかって自分でも自分に問いてます。まだ分からない部分がたくさんあって、何度も試行錯誤を重ねて自分の考えやロジックにチャレンジし、資料をどれだけ調べても読んでもやはり分からないことがありすぎて分からないまま放置しているのがよくあります。
ただ、この部分が大事だと直感的に感じて手が勝手に動いて、問題意識しながらテストの間でもだんだん面白くなってきました。そして自分も今回の検証からこれまでPromiseのステータスやタスク、タスクキューへの認識があやふやだったところ、間違っている部分に気づいていましたので、何日もかけたけど自分のなかではとても有意義な時間でした。
下は同じ概念で簡潔化にしたコードです。
// thenable vs. promise
class Thenable {
then(resolve, reject) {
setTimeout(
() => resolve('I am Thenable Object'),
1000
)
}
}
async function thenable() {
let str = await new Thenable()
console.log(str)
}
//
function PromiseToSet() {
return new Promise((resolve, reject) => {
setTimeout(
() => resolve('I am Promise'),
1000
)
})
}
async function test() {
let str = await PromiseToSet();
console.log(str);
}
thenable();
test();
// I am Thenable Object
// I am Promise
タイマーへが非同期の動きを観察するコードです。
最後にもう少し自分なりにまとめてみたいと思います。
自分から見れば、JSの非同期操作/非同期処理の実現というのは、タスク順とは関係なくタスク各々の処理を実行するところです。しかしMicrotask Queue隊列に入った実行待ちのコールバックは、実質上同期に動いています。
そうでないと、Call stack > Microtask Queue > Macrotask Queueのような優先度づけたり、Microtask Queueでは順番通りに動く必要がありません。すべてがsingle-threadのベースからなるプロセスだからと思います。
synchronous vs. asynchronous in async function
気になってしょうがないパートです。async functionは普通の関数と違い、非同期generatorに近い概念からなる関数というのがわかりますが、中身の動き、そしてawaitを加えたらどうなるのでしょうか?
まずはawaitからです。awaitは右にある値が解決されたPromiseを期待するが、そうでない場合は一度Promiseでラッピングして非同期処理を行わせます。しかし(Microtask queueから返した)結果を待たねばならないので下のコードが同期に動くしかありません。
(今から思い返せば、awaitは大きな処理ならばある意味で中身をブロッキング?...するかもしれないけど、async関数自体はmain()の処理を妨げないように設計されていると思います。普通の関数とは一番の違いです。)
// synchronous in async function
function getFoo() {
return 'Foo'
}
function getBar() {
return 'Bar'
}
async function syncTest() {
let foo = await getFoo();
let bar = await getBar();
return [foo, bar];
}
syncTest().
then((result) => console.log(result));
// [ 'Foo', 'Bar' ]
非同期の実現はMicrotaskにあるので、Promiseで包んだら同時実行ができるようになります。
// asynchronous in async function
async function asyncTest1() {
let [foo, bar] = await Promise.all(
[getFoo(), getBar()]
);
return [foo, bar];
}
asyncTest1()
.then((result) => console.log(result));
// [ 'Foo', 'Bar' ]
解決されたPromiseがMicrotask queueに移行しthen()の中身の処理を実行していきます。for Loopは同期に動くしかありません。
// Promise.allSettled(iterable)
async function stepByStep() {
// asynchronous
let data = Promise.allSettled(
[getFoo(), getBar(), Promise.reject('something wrong')]
);
// console.log(data instanceof Promise); // true
// asynchronous
data
.then((result) => {
// synchronous
for (let obj of result) {
if (obj.status === 'fulfilled') {
console.log(obj.value);
} else {
console.log(obj.reason);
}
}
});
}
stepByStep();
// Foo
// Bar
// something wrong
ここからresolved Promise(resolve())とunsettled Promise(Promise())の動きが気になって、
// asynchronous with synchronous
function test() {
// asynchronous => Promise.resolve(resolved Promise) => Microtask Queue(then(onFulfilled[, onRejected]))
// synchronous => then(onFulfilled()) => for Loop => Call stack
return Promise.resolve(['Foo', 'Bar']).then(
(result) => {
for (let i = 0; i < result.length; i++) {
console.log(result[i]);
}
}
);
}
test();
// Foo
// Bar
(ここはawait使う必要がない、一貫性のためにasyncを取りました。)
注釈が長すぎて読みづらいと思う方がいるかもしれないので、非同期の概念を教えてくれた Philip Roberts 氏が作ったサイトを借りて、ヴィジュアル化したシミュレーションは下にあります。(asyncキーワードがサポートされていませんが。)
function test() {
// asynchronous => Promise => Microtask, resolved Promise => Microtask queue(then())
// synchronous => then(onFulfilled()) => for Loop => Call stack
return new Promise((resolve) => {
resolve(['Foo', 'Bar'])
}).then((result) => {
for (let i = 0; i < result.length; i++) {
console.log(result[i])
}
});
}
test();
// Foo
// Bar
ここからまたawaitに戻ります。
非同期Promiseと非同期APIsetTimeout(callback, timer)にawaitを加えたらどうなるでしょう。
// asynchronous with asynchronous
async function test() {
// asynchronous => Promise => Microtask, resolved Promise => Microtask queue
// asynchronous => setTimeout(callback, timer) => timer to Macrotask => callback to Macrotask queue =>
// callback => resolve => Microtask => resolve() => resolved Promise => Microtask queue
let str1 = new Promise((resolve) => {
setTimeout(() => resolve('first'), 2000);
});
let str2 = new Promise((resolve) => {
setTimeout(() => resolve('second'), 1000);
});
Promise.race([str1, str2]).then((result) => console.log(result));
}
test(); // second
//
// add "await"
// await make everything in async function acts synchronously
// because it needs to wait until Promise is resolved
async function test() {
let str1 = await new Promise((resolve) => {
setTimeout(() => resolve('first'), 2000);
});
let str2 = await new Promise((resolve) => {
setTimeout(() => resolve('second'), 1000);
});
Promise.race([str1, str2]).then((result) => console.log(result));
}
test(); // first
やはりawaitの待つ行為は関数内の動きを同調にしてしまいます。
でもこれだけでawaitを使うのが非同期の意味がなくなるなんて思わないです。逆に言うと何か準備を整えてから次のステップに行くときawaitを使うべきだと思います。それにawaitを通してエラーキャッチと処理、ブレークポイントとして使うのもありだと思います。そして最も大事なのは、awaitを通して後ろの値が解決されたPromiseではなくても、解決されたPromiseとしてMicrotask Queueに移行し、外側に何が同期/非同期に動作しているコードがあってもブロッキングしないことです。
// check: Is variable's problem? No.
async function test() {
Promise.race([
await new Promise((resolve) => {
setTimeout(() => resolve('first'), 2000);
}),
await new Promise((resolve) => {
setTimeout(() => resolve('second'), 1000);
})
]).then((result) => console.log(result));
}
test(); // first
//
async function test() {
Promise.race([
new Promise((resolve) => {
setTimeout(() => resolve('first'), 2000);
}),
new Promise((resolve) => {
setTimeout(() => resolve('second'), 1000);
})
]).then((result) => console.log(result));
}
test(); // second
ES2022 Top-level await expression
○ ES6 module × CommonJs
ちょうど新しい使い方を見かけたので、一緒にまとめようと思います。
ES2022から最上位のawaitを追加しました。これまで非同期処理を使用するモジュールから一部の値をimportするとundefinedになるリスクがあるということで、
// Problem
// awaiting.js
let output;
async function main() {
const dynamic = await import(someMission); // import another module
const data = await fetch(url);
output = someProcess(dynamic.default, data);
}
main();
export { output };
// note: if we import awaiting.js before actions in async function
// haven't completed, output will be undefined
// usage.js
import { output } from './awaiting.js';
function outputPlusValue(value) {
return output + value; // output could be undefined, it depends on loading
}
console.log(outputPlusValue(100));
最上位awaitが利用できるまでは、Promiseとしてexportするのが解決策のようでした。
// alternative: export Promise
let output;
export default (async function main() {
const dynamic = await import(someMission);
const data = await fetch(url);
output = someProcess(dynamic.default, data);
})();
export { output };
// note: here export Promise, when we use output in another file,
// we can use await to ensure it is loaded
そしてこれが最新の書き方です。
// solution: top-level await
// awaiting.js
const dynamic = import(someMission);
const data = fetch(url);
export const output = someProcess((await dynamic).default, await data);
// note: use await to ensure modules are loaded
// usage.js
import { output } from '.awaiting.js';
function outputPlusValue(value) {
return output + value; // now, output always has value
}
console.log(outputPlusValue(100));
(実はこの二つ書いたことないです(汗)。一応メモとして残します!)