【TypeScript】Promiseをほんの少しだけ触った備忘録

どうも JSネタです。

何も考えずに aync await してたら 参考にしたいソースコードが

function asyncFunction() {
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            // 成功
            resolve('Async Hello world');
        }, 16);
    });
}

って書いてあって何も分からなくなってしまった。

理解ついでにコードを書きつつブログを書いていきます。

環境はnode.js+TypeScript(3.7.2) です。

TypeScriptで書いた方がわかりやすいので

Promiseを返す関数の話

Promiseとは

  • 非同期処理を「操作」するもの
  • 成功時の処理、失敗時の処理を明示的に書ける。

簡単に書いてみる

function asyncFunction(): Promise<string> {
  return new Promise(function (resolve, reject) {
    setTimeout(function() {
      resolve('Async Hello World');
    })
  })
}

asyncFunction().then(function (val) {
  console.log(val); // => 'Async Hello world'
});

まず、asyncFunctionはPromiseを返す。

PromiseでTは非同期処理の終了時、最終的に欲しいものを入れれば良さそう。

後はここ、ちゃんとPromise返ってくるのは伝わるね。

  return new Promise(function (resolve, reject) {
    setTimeout(function() {
      resolve('Async Hello World');
    })
  })

型が謎なんですけど関数の返り値にPromise<string>を書いたタイミングでresolve関数の

引数が決定される・・・?

基本的に実際の非同期処理はここに書いていて、setTimeoutの第一引数がhandlerになっている感じです。

setTimeoutの第二引数に入れた時間が経過したら第一引数の関数を呼び出す感じかな。

setTimeOutのhandlerでresolveが呼び出せるの、正直スコープどうなってんねんって思います。

ちなみに直でこうも書けた。

new Promise(function (resolve, reject) {
  setTimeout(function() {
    resolve('Async Hello World2');
  })}).then(function (val) {
  console.log(val); // => 'Async Hello World2'

});

ちゃんと理解するためにasyncFunctionを使って新しい関数を作ってみる。

function asyncFunction1(): Promise<string> {
  return new Promise(function (resolve, reject) {
    setTimeout(function() {
      resolve('Async Hello World');
    })
  })
}

function asyncFunction2(): Promise<string> {
  return new Promise(function (resolve) {
    asyncFunction1().then(function (val) {
      resolve(val+'nyaan');
    })
  })
}

asyncFunction2().then(
  function (val) {
    console.log(val); // => Async Hello Worldnyaan
  }
);

rejectしてみる

function rejectAsync() {
  return new Promise(function (resolve, reject) {
    setTimeout(function () {
      reject(new Error('Error'))
    }, 10);
  });
}

rejectAsync().then(function (val) {
  console.log('絶対に呼ばれない')
}).catch(function (error) {
    // 呼ばれる
    console.log(error)
  }
);

ちなみに出力は以下。

Error: Error
    at Timeout._onTimeout (/Users/reud/WebstormProjects/typescript-promise-practice/src/index.ts:4:14)
    at ontimeout (timers.js:436:11)
    at tryOnTimeout (timers.js:300:5)
    at listOnTimeout (timers.js:263:5)
    at Timer.processTimers (timers.js:223:10)

Process finished with exit code 0

閑話休題

このa,b,c,fの4つは同じ動作をします。

function f() {
  console.log('hello world');
}

const a=f;
a();
const b = ()=>{console.log('hello world')};
b();
const c = ()=>console.log('hello world');
c();

だけどこれはダメ

function x() console.log('hello world');

typescriptの場合は関数の後はコードブロック({}のこと)しか受け付けてないんですかね・・・?

アロー関数ならコードブロックじゃなくても良くなるけど・・・

それはそうと、こっからはアロー関数使えるところは使っていきます。

実はrejectはなくても良い

無名関数書くのめんどくさいのでアロー関数で

new Promise(function(resolve) {
  resolve(42);
}).then((val)=> console.log(val)); // => 42

逆にrejectだけでも良い

new Promise(function(reject) {
  reject(new Error('42'));
}).then((val)=> console.log(val)); // => 42
> ts-node ./src/index.ts

Error: 42
    at /Users/reud/WebstormProjects/typescript-promise-practice/src/index.ts:2:10
    at new Promise (<anonymous>)
    at Object.<anonymous> (/Users/reud/WebstormProjects/typescript-promise-practice/src/index.ts:1:1)
    at Module._compile (internal/modules/cjs/loader.js:701:30)
    at Module.m._compile (/Users/reud/WebstormProjects/typescript-promise-practice/node_modules/ts-node/src/index.ts:493:23)
    at Module._extensions..js (internal/modules/cjs/loader.js:712:10)
    at Object.require.extensions.(anonymous function) [as .ts] (/Users/reud/WebstormProjects/typescript-promise-practice/node_modules/ts-node/src/index.ts:496:12)
    at Module.load (internal/modules/cjs/loader.js:600:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:539:12)
    at Function.Module._load (internal/modules/cjs/loader.js:531:3)

Process finished with exit code 0

Promise.resolveの話

Promiseで非同期処理を書く時、ここまでなんとなくnew Promiseしていたけど、

Promiseオブジェクトを生成する際、Promise.resolveっていうのがある。

UTにめっちゃ使えそう

例えば、

Promise.resolve(42);

new Promise(resolve => resolve(42));

は同じ動作を行う。

けど、こうやると動作が変わる。

let fn = ()=>{throw new Error('error')};
let ret1 = new Promise((resolve => resolve(fn())));
let ret2 = Promise.resolve(fn()); // => 例外が発生

Promise.resolveって書いている以上失敗する可能性は書かない方が良さそうですね・・・

メソッドチェーン

Promiseオブジェクトに.thenでメソッドのチェーンができます。

let fn = ()=>console.log('hello world');
let fn2 = ()=>console.log('hello world2');
let fn3 = ()=>console.log('hello world3');
const promise:Promise<void> = Promise.resolve(fn());
promise.then(fn2).then(fn3);
/*
hello world
hello world2
hello world3
 */

ちなみに.catchはPromiseオブジェクトがしくった場合だけでなく、.thenでしくじっても

キャッチしてくれます。

(throw new Error()が一文じゃないのか、{}がfn2のアロー関数で必要だった

。)

そして、エラーの起きた.then以降は実行されなくなります。

let fn = ()=>console.log('hello world');
let fn2 = ()=> {throw new Error('error in fn2')};
let fn3 = ()=>console.log('hello world3');
const promise:Promise<void> = Promise.resolve(fn());
promise.then(fn2).then(fn3).catch((e)=> console.log(`error = ${e}`));

実行するとこう。

> ts-node ./src/index.ts

hello world
error = Error: error in fn2

逐次実行の理解はまた今度・・・

所感

JS書いてる人、高階関数強そう。

参考

Licensed under CC BY-NC-ND 4.0
Built with Hugo
テーマ StackJimmy によって設計されています。