ПРИМЕЧАНИЕ. finally
теперь является стандартной частью обещаний JavaScript, поэтому вы должны сделать следующее:
thePromise.then(result => doSomething(result)
.catch(error => handleOrReportError(error))
.finally(() => doSomethingAfterFulfillmentOrRejection());
Ответ до finally
был стандартным:
Если вы возвращаете значение из catch
, вы можете просто использовать then
в результате catch
.
thePromise.then(result => doSomething(result)
.catch(error => handleErrorAndReturnSomething(error))
.then(resultOrReturnFromCatch => /* ... */);
... но это означает, что вы преобразуете отказ в выполнение (возвращая что-то из catch
, а не выбрасывая или возвращая отклоненное обещание), и полагаетесь на этот факт.
Если вам нужно что-то, что прозрачно передает выполнение/отклонение без его изменения, в обещаниях ES2015 ("ES6") нет ничего, что делало бы это (редактировать: опять же, теперь есть), но это легко написать (это в ES2015, но у меня перевод ES5 ниже):
{
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; })
);
}
});
}
Пример:
{
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);
}
);
}
Пара замечаний по этому поводу:
Обратите внимание на использование this.constructor
, так что мы вызываем resolve
для любого типа обещания (включая возможный подкласс), созданного исходным обещанием; это согласуется с тем, как Promise.resolve
и другие работают и являются важной частью поддержки подклассов обещаний.
Вышеприведенное намеренно не включает какой-либо аргумент обратного вызова finally
и не указывает, было ли обещание выполнено или отклонено, чтобы соответствовать finally
в классической структуре try-catch-finally
. Но при желании можно было бы легко передать часть этой информации обратному вызову.
Точно так же в приведенном выше примере не используется значение, возвращаемое обратным вызовом finally
, за исключением того, что, если это промис, он ожидает выполнения промиса, прежде чем продолжить цепочку.
Вот перевод ES5 этого:
(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;
});
}
);
}
});
})();
Пример:
(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);
}
);
}
Я думаю, что это самый простой способ интегрировать эту функциональность в полифилл Promise в ES5.
Или, если вы предпочитаете создать подкласс Promise
, а не изменять его прототип:
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;
})();
Пример:
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);
}
);
}
Вы сказали, что хотите сделать это без расширения подклассов Promise.prototype
или. В ES5 служебную функцию было бы чрезвычайно неудобно использовать, потому что вам нужно было бы передать ей промис для выполнения действия, что совершенно не соответствовало бы обычному использованию промиса. В ES2015 можно сделать что-то более естественное, но вызвать его все равно сложнее, чем изменить прототип или создать подкласс:
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; })
];
}
})();
Применение:
thePromise.then(...always(/*..your function..*/)).
Обратите внимание на использование оператора распространения (поэтому он не будет работать в ES5), поэтому always
может передавать оба аргумента then
.
Пример:
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);
}
);
}
В комментариях вы выразили опасение, что finally
не дождутся обещанного; вот этот последний пример always
снова, с задержками, чтобы продемонстрировать, что это так:
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);
}
);
}
person
T.J. Crowder
schedule
08.08.2016
then
илиcatch
и предоставить соответствующие обратные вызовы, которые будут вызываться всякий раз, когда позже ваше обещание разрешается или отклоняется. - person Redu   schedule 08.08.2016