En utilisant le modèle de conception Promises, est-il possible d'implémenter les éléments suivants:
var a, promise
if promise.resolve
a = promise.responsevalue;
if promise.reject
a = "failed"
AFTER resolution/rejection. Not ASYNC!!
send a somewhere, but not asynchronously. //Not a promise
Ce que je recherche, c'est quelque chose comme finally
dans un try - catch
situation.
PS: j'utilise le polyfill ES6 Promise sur NodeJS
REMARQUE: finally
est maintenant une partie standard des promesses de JavaScript, vous feriez donc ceci:
thePromise.then(result => doSomething(result)
.catch(error => handleOrReportError(error))
.finally(() => doSomethingAfterFulfillmentOrRejection());
La réponse d'avant finally
était standard:
Si vous retournez une valeur de catch
, vous pouvez simplement utiliser then
sur le résultat de catch
.
thePromise.then(result => doSomething(result)
.catch(error => handleErrorAndReturnSomething(error))
.then(resultOrReturnFromCatch => /* ... */);
... mais cela signifie que vous convertissez un rejet en un accomplissement (en renvoyant quelque chose de catch
plutôt qu'en lançant ou en renvoyant une promesse rejetée), et vous reposez sur ce fait.
Si vous voulez quelque chose qui passe de manière transparente le long de l'accomplissement/rejet sans le modifier, il n'y a rien de prévu dans ES2015 ("ES6") qui promet que ( éditez: encore une fois, il y en a maintenant), mais c'est facile à écrire (c'est dans ES2015, mais j'ai une traduction ES5 ci-dessous):
{
let worker = (p, f, done) => {
return p.constructor.resolve(f()).then(done, done);
};
Object.defineProperty(Promise.prototype, "finally", {
value(f) {
return this.then(
result => worker(this, f, () => result),
error => worker(this, f, () => { throw error; })
);
}
});
}
Exemple:
{
let worker = (p, f, done) => {
return p.constructor.resolve(f()).then(done, done);
};
Object.defineProperty(Promise.prototype, "finally", {
value(f) {
return this.then(
result => worker(this, f, () => result),
error => worker(this, f, () => { throw error; })
);
}
});
}
test("p1", Promise.resolve("good")).finally(
() => {
test("p2", Promise.reject("bad"));
}
);
function test(name, p) {
return p.then(
result => {
console.log(name, "initial fulfillment:", result);
return result;
},
error => {
console.log(name, "initial rejection; propagating it");
throw error;
}
)
.finally(() => {
console.log(name, "in finally");
})
.then(
result => {
console.log(name, "fulfilled:", result);
},
error => {
console.log(name, "rejected:", error);
}
);
}
Quelques notes à ce sujet:
Notez l'utilisation de this.constructor
afin d'appeler resolve
sur tout type de promesse (y compris une sous-classe possible) qui a créé la promesse d'origine; cela correspond à la façon dont Promise.resolve
et d'autres fonctionnent, et est un élément important pour soutenir les promesses sous-classées.
Ce qui précède est intentionnellement n'inclut pas tout argument au rappel finally
, et aucune indication de si la promesse a été tenue ou rejetée, afin pour être cohérent avec finally
dans le classique try-catch-finally
structure. Mais si on le voulait, on pourrait facilement transmettre certaines de ces informations dans le rappel.
De même, ce qui précède n'utilise pas la valeur renvoyée par le rappel finally
sauf que si c'est une promesse, il attend la promesse de s'installer avant de laisser la chaîne continuer.
Voici la traduction ES5 de cela:
(function() {
function worker(ctor, f, done) {
return ctor.resolve(f()).then(done, done);
}
Object.defineProperty(Promise.prototype, "finally", {
value: function(f) {
var ctor = this.constructor;
return this.then(
function(result) {
return worker(ctor, f, function() {
return result;
});
},
function(error) {
return worker(ctor, f, function() {
throw error;
});
}
);
}
});
})();
Exemple:
(function() {
function worker(ctor, f, done) {
return ctor.resolve(f()).then(done, done);
}
Object.defineProperty(Promise.prototype, "finally", {
value: function(f) {
var ctor = this.constructor;
return this.then(
function(result) {
return worker(ctor, f, function() {
return result;
});
},
function(error) {
return worker(ctor, f, function() {
throw error;
});
}
);
}
});
})();
test("p1", Promise.resolve("good")).finally(function() {
test("p2", Promise.reject("bad"));
});
function test(name, p) {
return p.then(
function(result) {
console.log(name, "initial fulfillment:", result);
return result;
},
function(error) {
console.log(name, "initial rejection; propagating it");
throw error;
}
)
.finally(function() {
console.log(name, "in finally");
})
.then(
function(result) {
console.log(name, "fulfilled:", result);
},
function(error) {
console.log(name, "rejected:", error);
}
);
}
Je pense que c'est le moyen le plus simple d'intégrer cette fonctionnalité dans un polyfill Promise dans ES5.
Ou si vous préférez sous-classer Promise
plutôt que de modifier son prototype:
let PromiseX = (() => {
let worker = (p, f, done) => {
return p.constructor.resolve(f()).then(done, done);
};
class PromiseX extends Promise {
finally(f) {
return this.then(
result => worker(this, f, () => result),
error => worker(this, f, () => { throw error; })
);
}
}
PromiseX.resolve = Promise.resolve;
PromiseX.reject = Promise.reject;
return PromiseX;
})();
Exemple:
let PromiseX = (() => {
let worker = (p, f, done) => {
return p.constructor.resolve(f()).then(done, done);
};
class PromiseX extends Promise {
finally(f) {
return this.then(
result => worker(this, f, () => result),
error => worker(this, f, () => { throw error; })
);
}
}
PromiseX.resolve = Promise.resolve;
PromiseX.reject = Promise.reject;
return PromiseX;
})();
test("p1", PromiseX.resolve("good")).finally(
() => {
test("p2", PromiseX.reject("bad"));
}
);
function test(name, p) {
return p.then(
result => {
console.log(name, "initial fulfillment:", result);
return result;
},
error => {
console.log(name, "initial rejection; propagating it");
throw error;
}
)
.finally(() => {
console.log(name, "in finally");
})
.then(
result => {
console.log(name, "fulfilled:", result);
},
error => {
console.log(name, "rejected:", error);
}
);
}
Vous avez dit que vous vouliez le faire sans étendre le Promise.prototype
ou sous-classement. Dans ES5, une fonction utilitaire serait extrêmement difficile à utiliser, car il faudrait lui passer la promesse d'agir, qui serait complètement hors d'étape avec l'utilisation normale de la promesse. Dans ES2015, il est possible de faire quelque chose de plus naturel mais c'est toujours plus pénible d'appeler que de modifier le prototype ou de sous-classer:
let always = (() => {
let worker = (f, done) => {
return Promise.resolve(f()).then(done, done);
};
return function always(f) {
return [
result => worker(f, () => result),
error => worker(f, () => { throw error; })
];
}
})();
Usage:
thePromise.then(...always(/*..your function..*/)).
Notez l'utilisation de l'opérateur d'étalement (c'est pourquoi cela ne fonctionnera pas dans ES5), donc always
peut fournir les deux arguments à then
.
Exemple:
let always = (() => {
let worker = (f, done) => {
return Promise.resolve(f()).then(done, done);
};
return function always(f) {
return [
result => worker(f, () => result),
error => worker(f, () => { throw error; })
];
}
})();
test("p1", Promise.resolve("good")).then(...always(
() => {
test("p2", Promise.reject("bad"));
}
));
function test(name, p) {
return p.then(
result => {
console.log(name, "initial fulfillment:", result);
return result;
},
error => {
console.log(name, "initial rejection; propagating it");
throw error;
}
)
.then(...always(() => {
console.log(name, "in finally");
}))
.then(
result => {
console.log(name, "fulfilled:", result);
},
error => {
console.log(name, "rejected:", error);
}
);
}
Dans les commentaires, vous avez dit craindre que le finally
n'attende la promesse; voici à nouveau le dernier exemple de always
, avec des délais pour démontrer qu'il le fait:
let always = (() => {
let worker = (f, done) => {
return Promise.resolve(f()).then(done, done);
};
return function always(f) {
return [
result => worker(f, () => result),
error => worker(f, () => { throw error; })
];
}
})();
test("p1", 500, false, "good").then(...always(
() => {
test("p2", 500, true, "bad");
}
));
function test(name, delay, fail, value) {
// Make our test promise
let p = new Promise((resolve, reject) => {
console.log(name, `created with ${delay}ms delay before settling`);
setTimeout(() => {
if (fail) {
console.log(name, "rejecting");
reject(value);
} else {
console.log(name, "fulfilling");
resolve(value);
}
}, delay);
});
// Use it
return p.then(
result => {
console.log(name, "initial fulfillment:", result);
return result;
},
error => {
console.log(name, "initial rejection; propagating it");
throw error;
}
)
.then(...always(() => {
console.log(name, "in finally");
}))
.then(
result => {
console.log(name, "fulfilled:", result);
},
error => {
console.log(name, "rejected:", error);
}
);
}
Code ES2015:
promise.then(val => val).catch(() => "failed").then(a => doSomethigWithA(a));