1. ホーム
  2. javascript

値のフェッチ時に、未定義のプロパティ 'xx' が読み取れないのを回避する。

2022-02-10 12:27:51

この記事は 私のブログ 私のブログはGitHubでご覧いただけます。

値を取得するとき、特に連鎖した値を取得するとき、しばしば Cannot read property 'xx' of undefined これを避けるにはどうしたらよいのでしょうか。回避する方法をいくつか紹介します。

実績のあるライブラリのメソッドを使用する

これは最もシンプルな方法のひとつです。 _.get メソッドを使うか、Ramda を導入して R.path メソッドを使用すれば、上記のエラーのリスクを回避することができます。

この方法はとても効果的で便利なのですが、他の方法にも目を通してほしいのです

&&と||を使用する。

JavaScriptでは、&&や||演算子を使うと、次の例のように、最終的に返される値が必ずしもboolean型にならないことが分かっています。

console.log(undefined && "a"); //undefined
console.log("a" && "b"); //b
console.log(undefined || "a"); //a
console.log("a" || "b"); //a

&&: 最初の項がfalsy(ダミー値、ブール文脈では「偽」に変換されると判断された値)である場合は最初の項を返し、そうでない場合は2番目の項を返します。

||: 第一の項が偽の場合、第二の項を返し、そうでない場合は第一の項を返します。

このルールを使って、値を取得することができます。

まず、データをフィッティングしてみましょう

const artcle = {
    authorInfo: {
        author: "Bowen"
    },
    artcleInfo: {
        title: "title",
        timeInfo: {
            publishTime: "today"
        }
    }
};

次に、安全な取得のために && と || を使用します。

console.log(artcle.authorInfo && artcle.authorInfo.author); //Bowen
console.log(artcle.timeInfo && artcle.timeInfo.publishTime); //undefined
console.log(artcle.artcleInfo &&
        artcle.artcleInfo.timeInfo &&
        artcle.artcleInfo.timeInfo.publishTime
); //today

console.log((artcle.authorInfo || {}).author); //Bowen
console.log((artcle.timeInfo || {}).publishTime); //undefined
console.log(((artcle.artcleInfo || {}).timeInfo || {}).publishTime); //today

どちらの方法もエレガントではなく、連鎖した短いフェッチにしか使えないことは簡単にわかります。また、ネストが深くなりすぎると、&&を使うと長いコードが必要になり、||を使うと多くのネストした括弧が必要になります

分解された代入にデフォルト値を使用する

es6の分解代入を利用して、プロパティにデフォルト値を与え、エラーを回避することができるのです。例として、上記のアートクルデータは以下のようになります。

const { authorInfo: { author } = {} } = artcle;
console.log(author); //Bowen

上記のようにすると、多くの変数をいきなり公開することになるので、次のように関数をラップするだけでよいのです。

const getAuthor = ({ authorInfo: { author } = {} } = {}) => author;
console.log(getAuthor(artcle)); //Bowen

これは変数を公開しないので、getAuthor関数を再利用することができ、よりエレガントです。

try catchの使用

取り込み中にエラーが発生することもあるので、当然ながら try catch を使用して、事前にエラーをキャッチすることができます。

let author, publishTime;
try {
    author = artcle.authorInfo.author;
} catch (error) {
    author = null;
}
try {
    publishTime = artcle.timeInfo.publishTime;
} catch (error) {
    publishTime = null;
}
console.log(author); //Bowen
console.log(publishTime); //null

この方法の悪い点は、1 つの try catch ステートメントで複数のフェッチを行うことができないことです。なぜなら、エラーが発生するとすぐに catch ステートメントに移動してしまうからです。

このフローを最適化するために、汎用的な関数である

const getValue = (fn, defaultVaule) => {
    try {
        return fn();
    } catch (error) {
        return defaultVaule;
    }
};
const author = getValue(() => artcle.authorInfo.author);
const publishTime = getValue(() => artcle.timeInfo.publishTime);
console.log(author); //Bowen
console.log(publishTime); //undefined

プロキシを使用する

ウェブで見た、es6でプロキシを活用した非常に興味深い書き込みを紹介します。

const pointer = function(obj, path = []) {
    return new Proxy(function() {}, {
        get: function(target, key) {
            return pointer(obj, path.concat(key));
        },
        apply: function(target, object, args) {
            let value = obj;
            for (let i = 0; i < path.length; i++) {
                if (value == null) {
                    break;
                }
                value = value[path[i]];
            }
            if (value === undefined) {
                value = args[0];
            }
            return value;
        }
    });
};
const proxyArtcle = pointer(artcle);
console.log(proxyArtcle.authorInfo.author()); //Bowen
console.log(proxyArtcle.publishTime()); //undefined

原理は比較的単純で、ポインタメソッドがプロキシオブジェクトとして null 関数を持つ Proxy インスタンスを返し、値が取得されるたびにキーが保存されて proxyArtcle.authorInfo.author 例えば、次のように等価です。 pointer(artcle, ["authorInfo", "author"]) . applyはパス配列を順番に繰り返し、値を取り続けられないと判断するとループから抜け出します。

プロキシについてまだ学んでいない方は、数分かけて理解してください。 プロキシ

これはよりエレガントなソリューションのように思えますが、プロキシはブラウザとノードのバージョンによって制限され、ポリフィルは不可能なので、実際のアプリケーションには考えることが多すぎます。

オプションのチェーニング

これはまだ提案段階の新機能で、以下の通りです。 tc39/proposal-optional-chaining が、すでに使用できるバベルがあります。 babel-plugin-proposal-optional-chaining

この機能は以下のように使うことができます。

console.log(artcle?.authorInfo?.author); //Bowen
console.log(artcle?.timeInfo?.publishTime) //undefined

この方法は完璧に近く、実際に実装されることが期待できます