Javascript-форум (https://javascript.ru/forum/)
-   Общие вопросы Javascript (https://javascript.ru/forum/misc/)
-   -   Как асинхронно вызвать resolve() вне Promise? (https://javascript.ru/forum/misc/66469-kak-asinkhronno-vyzvat-resolve-vne-promise.html)

Malleys 16.12.2016 21:23

Как асинхронно вызвать resolve() вне Promise?
 
Всем привет! Понадобился Promise, который бы выполнялся, когда объект с данными создан. Необходимо, чтобы сторонний код "приостанавливал" выполнение моего скрипта, пока объект не будет готов.

Я считаю, что нет необходимости запихивать длинные куски кода в Promise только ради того, чтобы вызвать resolve() или reject().
Поэксперементировал с аксессорами...

var setter;
var promise = new Promise(function(resolve, reject) {
	setter = function(value) {
		if(value) {
			resolve();
		}
	};
});

Object.defineProperty(this, "objectCreated", {
	set: setter,
	get: function() {
		return promise;
	}
});

// много строк спустя...

this.objectCreated = true;


Hо пришлось отказаться от этой идеи, поскольку появляются лишние переменные. И выглядит это ужасно. Думаю, более элегантным (по аналогии с Promise.resolve()) решением проблемы является метод Promise.prototype.resolve(), который заставляет выполниться Promise.

Promise.prototype.resolve = function(value) {
	// как здесь вызвать resolve() ?
};

fuckingquest 17.12.2016 00:14

Цитата:

Сообщение от Malleys
который заставляет выполниться Promise.

А какой смысл его заставлять выполнится, если данные еще не готовы? Ведь промисы как раз и предназначены для упорядочивания асинхронных событий. Не совсем ясно, что Вы хотите. Ваш сторонний объект при готовности должен дернуть эту функцию, или что?

fuckingquest 17.12.2016 00:30

что же касается непосредственно вопроса:
Цитата:

Сообщение от Malleys
Как асинхронно вызвать resolve() вне Promise?

то это делается вот так, вроде:
promise = new Promise(function(resolve){
   setTimeout(function(){resolve(100)}, 1000)
})

anotherCode = function(){
  setTimeout(function(){
     Promise.resolve(promise).then(function(value){console.log(value)})
  }, 2000)
}

anotherCode()

хотя, по-сути, это, похоже, лишние костыли, можно и так это сделать:
promise = new Promise(function(resolve){
   setTimeout(function(){console.log("first run"); resolve(100)}, 1000)
})

anotherCode = function(){
  setTimeout(function(){
     console.log("second run")
     promise.then(function(value){console.log(value)})
  }, 2000)
}

anotherCode()


То есть, как только Вы сеттите коллбек, условно говоря "onresolve", тут же resolve(точней -- его внутреннее представление) и вызывается(на уровне реализации), если данные готовы.

Malleys 17.12.2016 11:28

Спасибо за мнение! К сожалению ваш код не решает проблему,
promise = new Promise(function(resolve){
	setTimeout(function(){console.log("first run"); resolve(100)}, 1000)
})
Здесь resolve(100) не может быть вызвано раньше, чем сработает setTimeout...



Цитата:

Сообщение от fuckingquest (Сообщение 438251)
А какой смысл его заставлять выполнится, если данные еще не готовы?

Они готовы, но находятся не внутри функций "промиса". Другой вариант -- заставить "промис" передумать, т. е. как вызвать reject() извне?

Простая обёртка вокруг каждого "промиса" позволяет добиться такого результата
function defer(promise) {
	var resolvingFunctions;
	var promise = new Promise(function(resolve, reject) {
		resolvingFunctions = { resolve, reject };
	});
	
	promise.resolve = resolvingFunctions.resolve;
	promise.reject = resolvingFunctions.reject;

	return promise;
}

// Пример
var promise = defer();
promise.then(function() {
	console.log("5+");
});

// "промис", выполнись!
promise.resolve();


Однако мне не нравится отдельный объект(который впринципе не нужен) и то, что придётся оборачивать "промисы", поэтому вопрос о том, как вызвать resolve() остаётся актуальным.
Т. е. как написать resolve/reject в Promise.prototype, я не представляю... Проблема в том, что нет доступа к resolvingFunctions из 8 пункта.

When the Promise function is called with argument executor the following steps are taken:
1. If NewTarget is undefined, throw a TypeError exception.
2. If IsCallable(executor) is false, throw a TypeError exception.
3. Let promise be OrdinaryCreateFromConstructor(NewTarget, "%PromisePrototype%", «‍[[PromiseState]], [[PromiseResult]], [[PromiseFulfillReactions]], [[PromiseRejectReactions]]» ).
4. ReturnIfAbrupt(promise).
5. Set promise's [[PromiseState]] internal slot to "pending".
6. Set promise's [[PromiseFulfillReactions]] internal slot to a new empty List.
7. Set promise's [[PromiseRejectReactions]] internal slot to a new empty List.'
8. Let resolvingFunctions be CreateResolvingFunctions(promise).
9. Let completion be Call(executor, undefined, «resolvingFunctions.[[Resolve]], resolvingFunctions.[[Reject]]»).
10. If completion is an abrupt completion, then
	a. Let status be Call(resolvingFunctions.[[Reject]], undefined, «completion.[[value]]»).
	b. ReturnIfAbrupt(status).
11. Return promise.

(Цитата из спецификации ECMA http://www.ecma-international.org/ec...omise-executor)

Эта цитата навеяла меня на то, что если resolvingFunctions в 9 пункте используются в функций, к которой есть доступ, то можно переписать конструктор "промиса"
(function(root, nativePromise) {
	function Promise(executor) {
		var resolvingFunctions;
		var promise = new nativePromise(function(resolve, reject) {
			resolvingFunctions = { resolve, reject };

			executor.call(undefined, resolve, reject);
		});

		promise.resolve = resolvingFunctions.resolve;
		promise.reject = resolvingFunctions.reject;

		return promise;
	}

	// это потребовалось, поскольку переопределён конструктор
	// и следовательно статичные методы улетели, возвращаем на место
	"race reject resolve".split(" ").forEach(function(method) {
		Promise[method] = nativePromise[method];
	});

	// теперь Promise.resolve() instanceof Promise === false
	// поскольку nativePromise !== Promise, исправляем
	Object.defineProperty(Promise, Symbol.hasInstance, {
		value: function(instance) {
			return instance instanceof nativePromise;
		}
	})

	Promise.prototype.constructor = Promise;

	root.Promise = Promise;
})(this, Promise);


Можно сказать проблема решена, если кто более гениально не придумает!

Впринципе теперь можно легко создать промис, который может передумать!
var promise = new Promise(function(resolve) {
	setTimeout(resolve, 2500);
});

promise.then(function() {
	alert("Свершилось!");
});

// Если вызвать эту функцию раньше чем выполнится "промис",
// то он никогда не выполнится
function nowIThinkAnotherWay() {
	promise.reject();
}


Рабочии пример http://codepen.io/Malleys/pen/VmqdeN?editors=0010

destus 17.12.2016 15:32

Malleys,
Цитата:

Можно сказать проблема решена
Для отмены XHR запросов работать не будет.

Как вариант, можно сделать через потоки, чтобы не устраивать колбасу с переписыванием функционала реализации промиса в браузере.
<script src='https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.0.1/Rx.js'></script>
<button id='b1'>start</button>
<button id='b2'>cancel</button>
<script>
let [b1, b2] = document.querySelectorAll('button'),
	o = Rx.Observable.fromEvent(b1, 'click')
						 .switchMap(() => Rx.Observable.timer(2000).takeUntil(Rx.Observable.fromEvent(b2, 'click')))				 
					 
o.subscribe(() => document.body.style.backgroundColor = "#" + ("000000" + (Math.random() * 0xffffff | 0).toString(16)).slice(-6))
</script>

fuckingquest 18.12.2016 00:09

Цитата:

Сообщение от Malleys
"промис", выполнись!

У Вас там нет никакого асинхронного кода, это бессмысленная затея.

Malleys 18.12.2016 06:50

Цитата:

Сообщение от fuckingquest (Сообщение 438305)
У Вас там нет никакого асинхронного кода, это бессмысленная затея.

Ну я и написал про тот defer(), что
Цитата:

Сообщение от Malleys (Сообщение 438265)
мне не нравится отдельный объект(который впринципе не нужен)


Malleys 18.12.2016 07:16

Цитата:

Сообщение от destus (Сообщение 438281)
Malleys, для отмены XHR запросов работать не будет.

Да, поскольку Promise.prototype.reject === undefined
Мой способ добавляет reject во время создания экземпляра "промиса".

(function(root, nativeFetch) {
	function fetch() {
		var args = arguments;
		return new Promise(function(resolve, reject) {
			nativeFetch.apply(null, args).then(resolve, reject);
		});
	}

	root.fetch = fetch;
})(this, fetch);


Цитата:

Сообщение от destus (Сообщение 438281)
<script src='https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.0.1/Rx.js'></script>

Я не особый любитель больших библиотек. Rx.js содержит более 14000 строк кода и он избыточен для задачи, которая может быть решена в 39 строк кода.

destus 18.12.2016 08:00

Malleys,
Rx.js это модульная библиотека, как и Lodash. Не нужно подключать её целиком, если скажем в скриптах используется Observable как сущность и 3-4 оператора. Достаточно подключить только нужное. А задачи, решаемые через промисы, можно решать и потоками, только делать это более качественно.

Вот так например выглядит автокомплит с википедии.
import { Subject }  from 'rxjs/Subject';

private searchTermStream = new Subject<string>();

searchTermStream
      .debounceTime(300)
      .distinctUntilChanged()
      .switchMap((term: string) => this.wikipediaService.search(term));

search(term: string) { this.searchTermStream.next(term); }


Часовой пояс GMT +3, время: 11:27.