Мені важче пояснити, ніж показати приклад, тому ось дуже проста реалізація того, що може бути відстрочкою / обіцянкою.
Застереження: Це не функціональна реалізація, і деякі частини специфікації Promise / A відсутні, це лише для пояснення основи обіцянок.
tl; dr: Перейдіть до розділу Створення класів та прикладу, щоб побачити повну реалізацію.
Обіцянка:
Спочатку нам потрібно створити об’єкт-обіцянку з масивом зворотних викликів. Я почну працювати з об'єктами, тому що це зрозуміліше:
var promise = {
callbacks: []
}
тепер додайте зворотні виклики методом тоді:
var promise = {
callbacks: [],
then: function (callback) {
callbacks.push(callback);
}
}
І нам також потрібні зворотні виклики помилок:
var promise = {
okCallbacks: [],
koCallbacks: [],
then: function (okCallback, koCallback) {
okCallbacks.push(okCallback);
if (koCallback) {
koCallbacks.push(koCallback);
}
}
}
Відкласти:
Тепер створіть об'єкт defer, який буде мати обіцянку:
var defer = {
promise: promise
};
Відстрочку потрібно вирішити:
var defer = {
promise: promise,
resolve: function (data) {
this.promise.okCallbacks.forEach(function(callback) {
window.setTimeout(function () {
callback(data)
}, 0);
});
},
};
І потрібно відхилити:
var defer = {
promise: promise,
resolve: function (data) {
this.promise.okCallbacks.forEach(function(callback) {
window.setTimeout(function () {
callback(data)
}, 0);
});
},
reject: function (error) {
this.promise.koCallbacks.forEach(function(callback) {
window.setTimeout(function () {
callback(error)
}, 0);
});
}
};
Зверніть увагу, що зворотні виклики викликаються з часом очікування, щоб код завжди був асинхронним.
І це те, що потрібно для базового відкладання / обіцянки впровадження.
Створіть класи та приклад:
Тепер давайте перетворимо обидва об’єкти на класи, спочатку обіцянку:
var Promise = function () {
this.okCallbacks = [];
this.koCallbacks = [];
};
Promise.prototype = {
okCallbacks: null,
koCallbacks: null,
then: function (okCallback, koCallback) {
okCallbacks.push(okCallback);
if (koCallback) {
koCallbacks.push(koCallback);
}
}
};
А тепер відстрочка:
var Defer = function () {
this.promise = new Promise();
};
Defer.prototype = {
promise: null,
resolve: function (data) {
this.promise.okCallbacks.forEach(function(callback) {
window.setTimeout(function () {
callback(data)
}, 0);
});
},
reject: function (error) {
this.promise.koCallbacks.forEach(function(callback) {
window.setTimeout(function () {
callback(error)
}, 0);
});
}
};
І ось приклад використання:
function test() {
var defer = new Defer();
serverCall(function (request) {
if (request.status === 200) {
defer.resolve(request.responseText);
} else {
defer.reject(new Error("Status code was " + request.status));
}
});
return defer.promise;
}
test().then(function (text) {
alert(text);
}, function (error) {
alert(error.message);
});
Як бачите, основні деталі прості і маленькі. Він зростатиме, коли ви додасте інші параметри, наприклад, роздільну здатність кількох обіцянок:
Defer.all(promiseA, promiseB, promiseC).then()
або ланцюжок обіцянок:
getUserById(id).then(getFilesByUser).then(deleteFile).then(promptResult);
Щоб прочитати більше про технічні характеристики: CommonJS Promise Specification . Зверніть увагу, що основні бібліотеки (Q, when.js, rsvp.js, node-promis, ...) відповідають специфікаціям Promises / A.
Сподіваюся, я був досить чітким.
Редагувати:
Як запитували в коментарях, я додав дві речі в цій версії:
- Тоді можливість дати обіцянку, незалежно від того, який статус вона має.
- Можливість ланцюга обіцянок.
Щоб мати змогу зателефонувати на обіцянку, коли її вирішено, потрібно додати статус до обіцянки, а коли буде викликано тоді, перевірити цей статус. Якщо статус вирішено або відхилено, просто виконайте зворотний виклик із його даними або помилкою.
Щоб мати змогу ланцюжково обіцяти, вам потрібно створити нову відстрочку для кожного дзвінка then
та, коли обіцянка вирішена / відхилена, вирішити / відхилити нову обіцянку з результатом зворотного дзвінка. Отже, коли обіцянка виконана, якщо зворотний виклик повертає нову обіцянку, вона прив’язується до обіцянки, поверненої разом із then()
. Якщо ні, обіцянка вирішується з результатом зворотного дзвінка.
Ось обіцянка:
var Promise = function () {
this.okCallbacks = [];
this.koCallbacks = [];
};
Promise.prototype = {
okCallbacks: null,
koCallbacks: null,
status: 'pending',
error: null,
then: function (okCallback, koCallback) {
var defer = new Defer();
this.okCallbacks.push({
func: okCallback,
defer: defer
});
if (koCallback) {
this.koCallbacks.push({
func: koCallback,
defer: defer
});
}
if (this.status === 'resolved') {
this.executeCallback({
func: okCallback,
defer: defer
}, this.data)
} else if(this.status === 'rejected') {
this.executeCallback({
func: koCallback,
defer: defer
}, this.error)
}
return defer.promise;
},
executeCallback: function (callbackData, result) {
window.setTimeout(function () {
var res = callbackData.func(result);
if (res instanceof Promise) {
callbackData.defer.bind(res);
} else {
callbackData.defer.resolve(res);
}
}, 0);
}
};
І відстрочка:
var Defer = function () {
this.promise = new Promise();
};
Defer.prototype = {
promise: null,
resolve: function (data) {
var promise = this.promise;
promise.data = data;
promise.status = 'resolved';
promise.okCallbacks.forEach(function(callbackData) {
promise.executeCallback(callbackData, data);
});
},
reject: function (error) {
var promise = this.promise;
promise.error = error;
promise.status = 'rejected';
promise.koCallbacks.forEach(function(callbackData) {
promise.executeCallback(callbackData, error);
});
},
bind: function (promise) {
var that = this;
promise.then(function (res) {
that.resolve(res);
}, function (err) {
that.reject(err);
})
}
};
Як бачите, він досить зріс.