質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Node.js

Node.jsとはGoogleのV8 JavaScriptエンジンを使用しているサーバーサイドのイベント駆動型プログラムです。

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

Q&A

解決済

1回答

1627閲覧

nodeのutil.promisifyの実装について

退会済みユーザー

退会済みユーザー

総合スコア0

Node.js

Node.jsとはGoogleのV8 JavaScriptエンジンを使用しているサーバーサイドのイベント駆動型プログラムです。

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

0グッド

2クリップ

投稿2018/01/19 12:26

https://github.com/nodejs/node/blob/master/lib/internal/util.js#L256-308

nodeのpromisifyの実装ですが、229行目でsetPrototypeOfを行っていると思います、
特に必要ないと思うのですが、なぜsetPrototypeOfを行っているかわかるでしょうか?
こういうケースで必要などあれば教えていただけないでしょうか?

該当部分をざっくり書くと、このような実装になっており★部分のsetPrototypeOfしている理由を知りたいです

javascript

1function promisify(original) { 2 function fn(...args) { 3 return new Promise((resolve)=>{ 4 original.call(this, ...args, (err, ...values) => { 5 resolve(promise, values[0]); 6 }) 7 } 8 } 9 10 Object.setPrototypeOf(fn, Object.getPrototypeOf(original)); // ★ setPrototypeOfをなぜ行う??? 11 Object.defineProperties(fn, Object.getOwnPropertyDescriptors(original)); 12 return fn 13} 14

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

私がそのコードを書いたわけではないので、以下は推測です。

実験としてその行を消すと、おそらく↓のテストが失敗するでしょう。
/test/parallel/test-util-promisify.js

ケースとしては以下が思いつきました。

  1. 別のコンテキストから得た関数を promisify するケース
  2. class Foo extends Function のインスタンスを promisify するケース
1. 別のコンテキストから得た関数を promisify するケース

vm.runInNewContext の結果として得られるオブジェクトは、vm.runInNewContext を実行しているのとは別のコンテキストで生成されたものになり、文字列にすると同じでも、instanceof の結果が異なります。

javascript

1const array1 = [1, 2, 3]; 2console.log(array1); 3// → [1, 2, 3] 4 5const array2 = vm.runInNewContext('[1, 2, 3]'); 6console.log(array2); 7// → [1, 2, 3] 8 9console.log([Array.isArray(array1), array1 instanceof Array]); 10// → [true, true] 11console.log([Array.isArray(array2), array2 instanceof Array]); 12// → [true, false]

ブラウザでも Web Worker や iframe を扱う時に注意が必要なポイントです。
参考文献:instanceof と複数のコンテキスト

setPrototypeOf により、別のコンテキストの関数を promisify した後の関数も、prototype としてその別のコンテキストの Function.prototype を持つことになります。

2. class Foo extends Function のインスタンスを promisify するケース

javascript

1const util = require('util'); 2 3class Foo extends Function { 4 constructor() { 5 super('callback', 'callback(null, 100)'); 6 } 7 get bar() { 8 return 'bar'; 9 } 10} 11 12function myPromisify(fn) { 13 return (...args) => new Promise((resolve, reject) => { 14 fn(...args, (error, value) => error ? reject(error) : resolve(value)); 15 }); 16} 17 18const foo = new Foo(); 19const promisified1 = util.promisify(foo); 20const promisified2 = myPromisify(foo); 21console.log([promisified1.bar, promisified2.bar]); 22// → ['bar', undefined] 23console.log([promisified1 instanceof Foo, promisified2 instanceof Foo]); 24// → [true, false]

↑のように、Function を継承するクラス Foo のインスタンスを promisify する場合に、setPrototypeOf しないと、結果は Foo のインスタンスではなくなります。


まとめると、error-first callback style な関数を async な関数にする以外の副作用を起こさないようにする処置なのではないかと思いますが、いかがでしょうか。

投稿2018/01/24 09:20

編集2018/01/24 09:34
退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2018/01/24 10:44

ご回答ありがとうございます。 凄くなっとくしました。 テストケースから予測する事や、extends Functionが可能な事など思いもつかない事もりだくさんな回答で大変勉強になりました。 javascriptは奥が深くて楽しいですね。 回答を見ただけなので、明日色々こねくり回して楽しみたいと思います。 ありがとうございます!
退会済みユーザー

退会済みユーザー

2018/01/24 12:07

疑問が解消できたようで、よかったです。Function を継承すると、インスタンスが関数呼び出しできるようになり、typeof は "object" ではなく "function" になります。ただし、回答のように super(...) の中に処理を書くと、グローバルスコープになってしまってクラスを作る意味がありません。なので、実際には Proxy を使うことになることを補足しておきます。 class Foo extends Function {  constructor() {   super();   return new Proxy(this, {    apply(target, thisArg, argumentsList) {     return target.executeFoo(...argumentsList);    }   });  }  executeFoo() {   // ここに処理を書く  } } ※全角スペースでインデントしています。ご注意ください。 https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Proxy
退会済みユーザー

退会済みユーザー

2018/01/25 10:09

追加情報ありがとうございます。 Proxyですね。確認してみます!
退会済みユーザー

退会済みユーザー

2018/01/27 08:10 編集

たびたびすみません。せっかく setPrototypeOf の話題なので、Proxy ではなくて、setPrototypeOf の例の方が良かったですね。 class Foo extends Function {  constructor() {   const foo = Object.setPrototypeOf(    (...args) => foo.bar(...args),    Object.getPrototypeOf(super())   );   return foo;  }  bar(...args) {   return args.join('-');  }  baz() {   return 'baz';  } } const foo = new Foo(); console.log(foo instanceof Foo); // → true console.log(foo(1, 2, 3)); // → '1-2-3' console.log(foo.bar(1, 2, 3)); // → '1-2-3' console.log(foo.baz(1, 2, 3)); // → 'baz' ※全角スペースでインデントしています。ご注意ください。 ※2018/1/27:コードに間違いがあったため、constructor内を修正いたしました。 これなら、Proxyが使えない環境でも動きます。
退会済みユーザー

退会済みユーザー

2018/01/25 11:16

さらなる追加情報ありがとうございます。 setPrototypeOfに関数を渡すイメージを持っていなかったので非常に参考になります。 通常は普通に関数定義すれば良いと思うので、パッと使いどころのイメージができませんが。 知識としては、持っておいて損はないので大変勉強になりました。 それにしても、奥が深くて震えあがりますね。 ご回答ありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問