Концепция - Как работает обещание?
Я посмотрел на многие реализации, и все они выглядят так по-разному, что я не могу понять, в чем суть обещания.
Если бы мне пришлось угадать, это просто функция, которая запускается при срабатывании обратного вызова.
Может ли кто-то реализовать самое простое обещание в несколько строк кода без цепочек.
Например из этого ответа
Фрагмент 1
var a1 = getPromiseForAjaxResult(ressource1url);
a1.then(function(res) {
append(res);
return a2;
});
Как функция передается then
знать, когда бежать.
То есть как это передается обратно к коду обратного вызова, который ajax запускает при завершении.
Фрагмент 2
// generic ajax call with configuration information and callback function
ajax(config_info, function() {
// ajax completed, callback is firing.
});
Как связаны эти два фрагмента?
Угадай:
// how to implement this
(function () {
var publik = {};
_private;
publik.then = function(func){
_private = func;
};
publik.getPromise = function(func){
// ??
};
// ??
}())
5 ответов
Может ли кто-то выполнить самое основное обещание в несколько строк?
Вот:
function Promise(fn) {
// takes a function as an argument that gets the fullfiller
var callbacks = [], result;
fn(function fulfill() {
if (result) return;
result = arguments;
for (var c;c=callbacks.shift();)
c.apply(null, arguments);
});
this.addCallback = function(c) {
if (result)
c.apply(null, result)
else
callbacks.push(c);
}
}
дополнительный then
с цепочкой (которая вам понадобится для ответа):
Promise.prototype.then = function(fn) {
var that = this;
return new Promise(function(c){
that.addCallback(function() {
var result = fn.apply(null, arguments);
if (result instanceof Promise)
result.addCallback(c);
else
c(result);
});
});
};
Как связаны эти два фрагмента?
ajax
называется из getPromiseForAjaxResult
функция:
function getPromiseForAjaxResult(ressource) {
return new Promise(function(callback) {
ajax({url:ressource}, callback);
});
}
По сути, обещание - это просто объект, у которого есть флаг, указывающий, было ли оно выполнено, и список функций, которые он поддерживает, чтобы уведомлять, если / когда оно выполнено. Код иногда может сказать больше, чем слова, поэтому вот очень простой, нереальный пример, который просто предназначен для передачи концепций:
// See notes following the code for why this isn't real-world code
function Promise() {
this.settled = false;
this.settledValue = null;
this.callbacks = [];
}
Promise.prototype.then = function(f) {
if (this.settled) {
f(this.settledValue); // See notes 1 and 2
} else {
this.callbacks.push(f);
}
// See note 3 about `then`
// needing a return value
};
Promise.prototype.settle = function(value) { // See notes 4 and 5
var callback;
if (!this.settled) {
this.settled = true;
this.settledValue = value;
while (this.callbacks.length) {
callback = this.callbacks.pop();
callback(this.settledValue); // See notes 1 and 2
}
}
};
Итак Promise
содержит состояние и функции, вызываемые при выполнении обещания. Акт урегулирования обещания обычно является внешним по отношению к Promise
сам объект (хотя, конечно, это зависит от фактического использования, вы можете комбинировать их - например, как с jQuery's ajax
[jqXHR
] объекты).
Опять же, вышесказанное является чисто концептуальным и в нем отсутствуют некоторые важные вещи, которые должны присутствовать в любой реальной реализации обещаний, чтобы это было полезно:
then
а такжеsettle
следует всегда вызывать обратный вызов асинхронно, даже если обещание уже выполнено.then
следует, потому что в противном случае вызывающая сторона не знает, будет ли обратный вызов асинхронным.settle
должен, потому что обратные вызовы не должны запускаться доsettle
вернулся. (Обещания ES2015 делают обе эти вещи. JQuery'sDeferred
нет.)then
а такжеsettle
должен гарантировать, что сбой в обратном вызове (например, исключение) не распространяется непосредственно на вызывающий кодthen
или жеsettle
, Это частично связано с № 1 выше, и в особенности с № 3 ниже.then
должен вернуть новое обещание, основанное на результате обратного вызова (тогда или позже). Это довольно важно для составления обещанных операций, но значительно усложнило бы вышесказанное. Любая разумная реализация обещаний делает.Нам нужны разные типы операций "урегулирования": "разрешить" (базовое действие выполнено успешно) и "отклонить" (не удалось). В некоторых случаях использования может быть больше состояний, но решаемые и отклоненные являются основными двумя. (Обещания ES2015 имеют решимость и отклоняются.)
Мы могли бы сделать
settle
(или отдельныйresolve
а такжеreject
) каким-то образом частным, так что только создатель обещания может выполнить его. (ES2015 обещает - и несколько других - сделать это, имеяPromise
конструктор принимает обратный вызов, который получаетresolve
а такжеreject
в качестве значений параметров, поэтому только код в этом обратном вызове может разрешить или отклонить [если код в обратном вызове не делает их общедоступными каким-либо образом].)
И т. Д.
Вот легкая реализация обещания, называемая "последовательность", которую я использую в своей повседневной работе:
(function() {
sequence = (function() {
var chained = [];
var value;
var error;
var chain = function(func) {
chained.push(func);
return this;
};
var execute = function(index) {
var callback;
index = typeof index === "number" ? index : 0;
if ( index >= chained.length ) {
chained = [];
return true;
}
callback = chained[index];
callback({
resolve: function(_value) {
value = _value;
execute(++index);
},
reject: function(_error) {
error = _error;
execute(++index);
},
response: {
value: value,
error: error
}
});
};
return {
chain: chain,
execute: execute
};
})();
})();
После инициализации вы можете использовать последовательность следующим образом:
sequence()
.chain(function(seq) {
setTimeout(function() {
console.log("func A");
seq.resolve();
}, 2000);
})
.chain(function(seq) {
setTimeout(function() {
console.log("func B");
}, 1000)
})
.execute()
Чтобы включить фактическую цепочку, вам нужно вызвать функцию resol () объекта seq, которую ваши обратные вызовы должны использовать в качестве аргумента.
Последовательность выставляет два открытых метода:
- цепочка - этот метод просто толкает ваши обратные вызовы в частный массив
- execute - этот метод использует рекурсию для правильного последовательного выполнения ваших обратных вызовов. Он в основном выполняет ваши обратные вызовы в том порядке, в котором вы их связали, передавая объект seq каждому из них. Как только текущий обратный вызов разрешен / отклонен, выполняется следующий обратный вызов.
Метод execute выполняет магию. Он передает объект 'seq' всем вашим обратным вызовам. Поэтому, когда вы вызываете seq.resolve() или seq.reject(), вы фактически вызываете следующий цепной обратный вызов.
Обратите внимание, что эта реализация хранит ответ только от ранее выполненного обратного вызова.
Дополнительные примеры и документацию см. По адресу: https://github.com/nevendyulgerov/sequence
Я реализовал один в ES7. 70 строк, если это считается как минимум, хотя я думаю, что State Machine - правильная парадигма для реализации. Результирующий код более понятен, чем множество if
с ИМХО. Полностью описан в этой статье.
Вот код:
const states = {
pending: 'Pending',
resolved: 'Resolved',
rejected: 'Rejected'
};
class Nancy {
constructor(executor) {
const tryCall = callback => Nancy.try(() => callback(this.value));
const laterCalls = [];
const callLater = getMember => callback => new Nancy(resolve => laterCalls.push(() => resolve(getMember()(callback))));
const members = {
[states.resolved]: {
state: states.resolved,
then: tryCall,
catch: _ => this
},
[states.rejected]: {
state: states.rejected,
then: _ => this,
catch: tryCall
},
[states.pending]: {
state: states.pending,
then: callLater(() => this.then),
catch: callLater(() => this.catch)
}
};
const changeState = state => Object.assign(this, members[state]);
const apply = (value, state) => {
if (this.state === states.pending) {
this.value = value;
changeState(state);
for (const laterCall of laterCalls) {
laterCall();
}
}
};
const getCallback = state => value => {
if (value instanceof Nancy && state === states.resolved) {
value.then(value => apply(value, states.resolved));
value.catch(value => apply(value, states.rejected));
} else {
apply(value, state);
}
};
const resolve = getCallback(states.resolved);
const reject = getCallback(states.rejected);
changeState(states.pending);
try {
executor(resolve, reject);
} catch (error) {
reject(error);
}
}
static resolve(value) {
return new Nancy(resolve => resolve(value));
}
static reject(value) {
return new Nancy((_, reject) => reject(value));
}
static try(callback) {
return new Nancy(resolve => resolve(callback()));
}
}
Вот простая реализация Promise, которая работает для меня.
function Promise(callback) {
this._pending = [];
this.PENDING = "pending";
this.RESOLVED = "resolved";
this.REJECTED = "rejected";
this.PromiseState = this.PENDING;
this._catch = function (error) {
console.error(error);
};
setTimeout(function () {
try {
callback.call(this, this.resolve.bind(this), this.reject.bind(this));
} catch (error) {
this.reject(error);
}
}.bind(this), 0)
};
Promise.prototype.resolve = function (object) {
if (this.PromiseState !== this.PENDING) return;
while (this._pending.length > 0) {
var callbacks = this._pending.shift();
try {
var resolve = callbacks.resolve;
if (resolve instanceof Promise) {
resolve._pending = resolve._pending.concat(this._pending);
resolve._catch = this._catch;
resolve.resolve(object);
return resolve;
}
object = resolve.call(this, object);
if (object instanceof Promise) {
object._pending = object._pending.concat(this._pending);
object._catch = this._catch;
return object;
}
} catch (error) {
(callbacks.reject || this._catch).call(this, error);
return;
}
}
this.PromiseState = this.RESOLVED;
return object;
};
Promise.prototype.reject = function (error) {
if (this.PromiseState !== this.PENDING) return;
this.PromiseState = this.REJECTED;
try {
this._catch(error);
} catch (e) {
console.error(error, e);
}
};
Promise.prototype.then = function (onFulfilled, onRejected) {
onFulfilled = onFulfilled || function (result) {
return result;
};
this._catch = onRejected || this._catch;
this._pending.push({resolve: onFulfilled, reject: onRejected});
return this;
};
Promise.prototype.catch = function (onRejected) {
// var onFulfilled = function (result) {
// return result;
// };
this._catch = onRejected || this._catch;
// this._pending.push({resolve: onFulfilled, reject: onRejected});
return this;
};
Promise.all = function (array) {
return new Promise(function () {
var self = this;
var counter = 0;
var finishResult = [];
function success(item, index) {
counter++;
finishResult[index] = item;
if (counter >= array.length) {
self.resolve(finishResult);
}
}
for(var i in array) {
var item = array[i];
if (item instanceof Promise) {
item.then(function (result) {
success(result,this);
}.bind(i), function (error) {
array.map(function (item) {
item.PromiseState = Promise.REJECTED
});
self._catch(error);
})
} else {
success(item, i);
}
}
});
};
Promise.race = function (array) {
return new Promise(function () {
var self = this;
var counter = 0;
var finishResult = [];
array.map(function (item) {
if (item instanceof Promise) {
item.then(function (result) {
array.map(function (item) {
item.PromiseState = Promise.REJECTED
});
self.resolve(result);
}, function (error) {
array.map(function (item) {
item.PromiseState = Promise.REJECTED
});
self._catch(error);
})
} else {
array.map(function (item) {
item.PromiseState = Promise.REJECTED
});
self.resolve(item);
}
})
});
};
Promise.resolve = function (value) {
return new Promise(function (resolve, reject) {
try {
resolve(value);
} catch (error) {
reject(error);
}
});
};
Promise.reject = function (error) {
return new Promise(function (resolve, reject) {
reject(error);
});
}
Вот абсолютный минимум обещанной архитектуры
function Promise(F) {
this.stack = [];
this.then = function(fn) {
if (isFunction(F)) {
this.stack.push(fn);
}
return this;
}
this.resolve = function(data) {
if (this.stack.length) {
var cb = this.stack[0];
this.stack.shift();
this.fcall(cb, data);
}
}
this.fcall = function(fn, data) {
fn.call(null, {
resolve: this.resolve.bind(this)
}, data);
}
function isFunction(f) {
return f && {}.toString.call(f) === '[object Function]';
}
if (isFunction(F)) {
this.then(F).resolve();
}
}
// --- below is a working implementation --- //
var bar = function(p) {
setTimeout(function() {
console.log("1");
p.resolve(2);
}, 1000);
}
var foo = function(p, num) {
setTimeout(function() {
console.log(num);
p.resolve(++num);
}, 1000);
}
new Promise(bar).then(foo)
.then(foo)
.then(foo)