web-dev-qa-db-ja.com

javascriptでPromiseを拡張する

私はjavascriptのクラスと継承について学んでいます。 Object.createのMDN docs からスタイルを取得したので、以下は既存のオブジェクトを拡張するかなり標準的な方法だと思いました。

私は「OK」そして「Yay!」を見ることを期待していました。コンソールで「こんにちは」ですが、代わりに次のエラーが発生します。

Uncaught TypeError: #<MyPromise> is not a promise
at new MyPromise (<anonymous>:5:17)
at <anonymous>:19:6

Promiseコンストラクターが例外をスローしているように見えます。これは、初期化するために指定したオブジェクトが単純なPromiseではないことがわかるためです。

PromiseコンストラクターでオブジェクトをPromiseオブジェクトであるかのように初期化して、クラスを拡張できるようにします。この一般的なパターンで動作するPromiseコンストラクターを作成しないのはなぜですか?私は何か間違ったことをしていますか?見てくれてありがとう!

MyPromise = function(message, ok) {
    var myPromise = this;
    this.message = message;
    this.ok = ok;
    Promise.call(this, function(resolve, reject) {
        if(this.ok) {
            console.log('ok');
            resolve(myPromise.message);
        } else {
            console.log('not ok');
            reject(myPromise.message);
        }   
    }); 
};  

MyPromise.prototype = Object.create(Promise.prototype);
MyPromise.prototype.constructor = MyPromise;

(new MyPromise('Hello', true))
    .then(function(response) {console.log('Yay! ' + response);})
    .except(function(error) {console.log('Aww! ' + error);});

私はもともと、次のように使用できるBatchAjaxクラスを作成しようとしていました。

(new BatchAjax([query1, query2]))
    .then(function(response) {console.log('Fires when all queries are complete.');}); 

本当に楽しかったです。

8
tobuslieven

ネイティブのPromiseクラス(ErrorArrayなど)は、サブクラス化のための古いES5スタイルのメカニズムで正しくサブクラス化することはできません。

Promiseをサブクラス化する正しい方法は、class構文を使用することです。

class MyPromise extends Promise {
}

例:

class MyPromise extends Promise {
    myMethod() {
        return this.then(str => str.toUpperCase());
    }
}

// Usage example 1
MyPromise.resolve("it works")
    .myMethod()
    .then(result => console.log(result))
    .catch(error => console.error(error));
    
// Usage example 2
new MyPromise((resolve, reject) => {
    if (Math.random() < 0.5) {
        resolve("it works");
    } else {
        reject(new Error("promise rejected; it does this half the time just to show that part working"));
    }
})
    .myMethod()
    .then(result => console.log(result))
    .catch(error => console.error(error));

classなしで、主にES5レベルの機能を使用してそれを行うことが目標である場合は、 Reflect.construct 。ご了承ください Reflect.constructclassのようなES2015の機能ですが、クラスを作成するES5スタイルを好むようです。

これを行う方法は次のとおりです。

// Create a constructor that uses `Promise` as its super and does the `super` call
// via `Reflect.construct`
const MyPromise = function(executor) {
    return Reflect.construct(Promise, [executor], MyPromise);
};
// Make `MyPromise` inherit statics from `Promise`
Object.setPrototypeOf(MyPromise, Promise);
// Create the prototype, add methods to it
MyPromise.prototype = Object.create(Promise.prototype);
MyPromise.prototype.constructor = MyPromise;
MyPromise.prototype.myMethod = function() {
    return this.then(str => str.toUpperCase());
};

次に、Promiseのように使用します。

MyPromise.resolve("it works")
    .myMethod()
    .then(result => console.log(result))
    .catch(error => console.error(error));

または

new MyPromise(resolve => resolve("it works"))
    .myMethod()
    .then(result => console.log(result))
    .catch(error => console.error(error));

等.

実例:

// Create a constructor that uses `Promise` as its super and does the `super` call
// via `Reflect.construct`
const MyPromise = function(executor) {
    return Reflect.construct(Promise, [executor], MyPromise);
};
// Make `MyPromise` inherit statics from `Promise`
Object.setPrototypeOf(MyPromise, Promise);
// Create the prototype, add methods to it
MyPromise.prototype = Object.create(Promise.prototype);
MyPromise.prototype.constructor = MyPromise;
MyPromise.prototype.myMethod = function() {
    return this.then(str => str.toUpperCase());
};

// Usage example 1
MyPromise.resolve("it works")
    .myMethod()
    .then(result => console.log(result))
    .catch(error => console.error(error));
    
// Usage example 2
new MyPromise((resolve, reject) => {
    if (Math.random() < 0.5) {
        resolve("it works");
    } else {
        reject(new Error("promise rejected; it does this half the time just to show that part working"));
    }
})
    .myMethod()
    .then(result => console.log(result))
    .catch(error => console.error(error));

MyPromiseのプロトタイプの変更を避けたい場合は、静的プロパティをコピーできますが、まったく同じではありません。

// Create a constructor that uses `Promise` as its super and does the `super` call
// via `Reflect.construct`
const MyPromise = function(executor) {
    return Reflect.construct(Promise, [executor], MyPromise);
};
// Assign the statics (`resolve`, `reject`, etc.) to the new constructor
Object.assign(
    MyPromise,
    Object.fromEntries(
        Reflect.ownKeys(Promise)
            .filter(key => key !== "length" && key !== "name")
            .map(key => [key, Promise[key]])
    )
);
// Create the prototype, add methods to it
MyPromise.prototype = Object.create(Promise.prototype);
MyPromise.prototype.constructor = MyPromise;
MyPromise.prototype.myMethod = function() {
    return this.then(str => str.toUpperCase());
};

もちろん、それを使用することは同じです。

実例:

// Create a constructor that uses `Promise` as its super and does the `super` call
// via `Reflect.construct`
const MyPromise = function(executor) {
    return Reflect.construct(Promise, [executor], MyPromise);
};
// Assign the statics (`resolve`, `reject`, etc.) to the new constructor
Object.assign(
    MyPromise,
    Object.fromEntries(
        Reflect.ownKeys(Promise)
            .filter(key => key !== "length" && key !== "name")
            .map(key => [key, Promise[key]])
    )
);
// Create the prototype, add methods to it
MyPromise.prototype = Object.create(Promise.prototype);
MyPromise.prototype.constructor = MyPromise;
MyPromise.prototype.myMethod = function() {
    return this.then(str => str.toUpperCase());
};

// Usage example 1
MyPromise.resolve("it works")
    .myMethod()
    .then(result => console.log(result))
    .catch(error => console.error(error));
    
// Usage example 2
new MyPromise((resolve, reject) => {
    if (Math.random() < 0.5) {
        resolve("it works");
    } else {
        reject(new Error("promise rejected; it does this half the time just to show that part working"));
    }
})
    .myMethod()
    .then(result => console.log(result))
    .catch(error => console.error(error));
3
T.J. Crowder

私の最新の解決策は、Promiseオブジェクトをthis.promiseとしてクラスに作成し、Promiseのすべてのインスタンスメソッドをオーバーライドしてthis.promiseオブジェクトに渡すことにより、Promiseから継承しているふりをすることです。陽気さが続きます。このアプローチの欠点を指摘してくださる方々を心から歓迎します。

私が見逃したほど明白なことは何もありません。

このコードをChromeコンソールに貼り付けると、機能しているように見えます。私が理解している限りでは、それは機能しているようです。

見てくれてありがとう。

BatchAjax = function(queries) {
    var batchAjax = this;
    this.queries = queries;
    this.responses = [];
    this.errorCount = 0;
    this.promise = new Promise(function(resolve, reject) {
        batchAjax.executor(resolve, reject);
    });
};
BatchAjax.prototype = Object.create(Promise.prototype);
BatchAjax.prototype.constructor = BatchAjax;
BatchAjax.prototype.catch = function(fail) {
    return this.promise.catch(fail);
}
BatchAjax.prototype.then = function(success, fail) {
    return this.promise.then(success, fail);
};
BatchAjax.prototype.executor = function(resolve, reject) {
    var batchAjax = this;
    $.each(this.queries, function(index) {
        var query = this;
        query.success = function (result) {
            batchAjax.processResult(result, index, resolve, reject);
        };
        query.error = function (jqXhr, textStatus, errorThrown) {
            batchAjax.errorCount++;
            var result = {jqXhr: jqXhr, textStatus: textStatus, errorThrown: errorThrown};
            batchAjax.processResult(result, index, resolve, reject);
        };
        $.ajax(query);
    });
};
BatchAjax.prototype.processResult = function(result, index, resolve, reject) {
    this.responses[index] = result;
    if (this.responses.length === this.queries.length) {
        if (this.errorCount === 0) {
            resolve(this.responses);
        } else {
            reject(this.responses);
        }
    }
};

// Usage
var baseUrl = 'https://jsonplaceholder.typicode.com';
(new BatchAjax([{url: baseUrl + '/todos/4'}, {url: baseUrl + '/todos/5'}]))
    .then(function(response) {console.log('Yay! ', response);})
    .catch(function(error) {console.log('Aww! ', error);});
3
tobuslieven