Javascript.RU

Создать новую тему Ответ
 
Опции темы Искать в теме
  #21 (permalink)  
Старый 05.07.2018, 11:07
Аватар для Malleys
Профессор
Отправить личное сообщение для Malleys Посмотреть профиль Найти все сообщения от Malleys
 
Регистрация: 20.12.2009
Сообщений: 1,714

...тема продолжается
Сообщение от arealhz Посмотреть сообщение
Как можно выполнить массив «промисов» последовательно, т. е. что бы элемент n выполнялся строго после завершения выполнения элемента n-1?
Конструктор «промиса» принимает в качестве аргумента функцию, которая вызывается сразу, когда создаётся объект Promise. Т. е. «промис» запускается сразу, как только создан. Поэтому, если вы создали массив «промисов», то они все сразу запускаются. Даже если вы не передали такой массив в качестве аргумента в функцию `Promise.all`.

Пусть у нас есть «промисы» p1, p2, p3, ... Вот один из вариантов, как их можно запустить последовательно...
[
	() => p1,
	() => p2,
	() => p3,
	() => p4
].reduce((seq, p) => seq.then(p), Promise.resolve());

`p1` запускается сразу, как создан, но функция `() => p1` позволяет отложить запуск до того времени, когда она будет вызвана. Обратите внимание, что метод массива `reduce` возвращает в примере выше «промис», который выполнится, когда будут последовательно выполнены все «промисы» из массива. Так что можно дальше вызывать метод `then`.

Вот ещё один из вариантов, как можно те же «промисы» запустить последовательно...
(async () => [
	await p1,
	await p2,
	await p3,
	await p4
])();

Этот вызов тоже возвращает «промис», который выполнится, когда будут последовательно выполнены все «промисы» из массива...

Сообщение от arealhz Посмотреть сообщение
Так же интересны варианты выполнения массивов «промисов» [...] по 10 за раз
Как вариант использовать итератор...
function* getTasks() {
	const a = Array.from(Array(66), x => 1400 * Math.random() + 100);

	for(let i = 0, len = a.length; i < len; i+=10)
		yield Promise.all(a.slice(i, i+ 10).map(delay));

	function delay(ms) {
		return new Promise(r => {
			setTimeout(() => {
				r(`done: ${ms}ms`);
			}, ms);
		});
	}
}

(async() => {
	for await(const task of getTasks()) {
		console.log(task);
	}
	console.log("all done");
})();


Сообщение от arealhz Посмотреть сообщение
...магия вне Хогвартса.
Просто сравните два примера, оба выражения обозначают одно и тоже, когда вычислены представляют «промис» — пустой контейнер.
(async function() {
    const response = await fetch("https://yesno.wtf/api");
    const { answer } = await response.json();
    console.log(`Ответ ${answer} без магии`);
})();

fetch("https://yesno.wtf/api")
	.then(response => response.json())
	.then(({answer}) => console.log(`Ответ ${answer} без магии`))
;


Ваша конструкция из поста №12 без async/await... они не индентичны в плане трансформации кода, он просто переписан так, чтобы вывод был одинаковый...
"use strict";
'esversion: 6';
function procData(val, i){
	return new Promise(resolve => setTimeout(() => {
		var ret = i + "\t" + val + " #";
		console.log(ret);
		resolve(ret);
	}, 3000));
}
 
[3, 5, "asdf", 8, 0]
	.reduce((seq, item, n) => seq
		.then(retArr => procData(item, n)
		.then(x => retArr.concat(x))
	), Promise.resolve([]))
	.then(console.log);


Ещё примеры async/await, оба вычисленных выражения обозначают одно и тоже...
(async function() { return 5 })();
Promise.resolve(5);


(async function() { throw "Упс!" })();

Promise.reject("Упс!");


Сообщение от arealhz
Оно работает, но я по прежнему не понимаю, почему все работает внутри безымянной функции с пометкой async, которая ещё и сразу вызывается, после объявления.
Давайте тогда не вызывать... `async function() { return 5; }` пример литерала для экземпляра класса AsyncFunction, который вычисляется также как `new AsyncFunction("return 5;")`, этот объект наследует от `Function`.

Такой объект содержит список выражений и объявлений, который выполняется при вызове функции, т. е. как обычная функция, однако ещё имеется выражение с ключевым словом await, например `await b;`. Давайте рассмотрим, как это действует.

Пусть у нас есть блок тела из обычной функций типа `Function` cо следующими выражениями: () => { a; b; c; return d; }
Когда фукция будет вызвана, сначала вычислится a затем b, c, d. Вычисленное d будет результатом вычисления функции. Порядок вычисления всегда линейный и все понимают как это работает.

Теперь рассмотрим блок тела из асинхронной функций типа `AsyncFunction` cо следующими выражениями: async () => { a; await b; c; return d; } Давайте его вычислим. Здесь действуют такие правила(только для вычисления списка выражении):

1. для самого левого выражения с меткой await внутри блока с меткой async:
	a. произведи следующую замену:
		i. A = await B; <всё-до-конца-блока> --> return B.then(async A => { <всё-до-конца-блока> }) если подходит
		ii. await B; <всё-до-конца-блока> --> return B.then(async () => { <всё-до-конца-блока> })
		iii. B --> Promise.resolve(B) если B не «промис»
    b. если ещё есть await см. п. 1
2. для всех блоков с меткой async:
	a. убери async
	b. return A --> return Promise.resolve(A) если A не «промис»
	c. throw A --> return Promise.reject(A)
	d. добавь в конец блока return Promise.resolve() если нет return


получается так(пусть b «промис»):
async () => { a; await b; c; return d; } // 1a
async () => { a; return b.then(async () => { c; return d; }); } //2a
() => { a; return b.then(async () => { c; return d; }); } //2a
() => { a; return b.then(() => { c; return d; }); } //2b
() => { a; return b.then(() => { c; return Promise.resolve(d); }); } // конечный результат


Впринципе видно, что тут нет никакой магии, это просто трансформация кода. Можно сказать, что async/await является синтактическим сахаром для выражения, которое получается после перевода его в обычный синтаксис.

Давайте ещё один пример вычислим, который я приводил выше...
(async function() { throw "Упс!" })(); //2a
(function() { throw "Упс!" })(); //2c
(function() { return Promise.reject("Упс!"); })(); // и ещё можно сделать вызов
Promise.reject("Упс!"); // конечный результат


Из этих преобразовании видно, что асинхронная функция всегда возвращает «промис»

Сообщение от Aetae
Promise же в свою очередь - это не что-то самостоятельное, а просто удобная обёртка над коллбэками
Асинхронные коллбэки это объекты типа AsyncFunction, а вот их вызов как раз таки интересен тем, что они выдают удобную обёртку над возвращаемым значением. Это обобщённая на все типы кроме Promise обёртка. При попытке создать обёртку уже на существующей обёртке, мы получаем интересный эффект — ту же самую обёртку по типу. Означает ли это, что в JavaScript'е не существует схлапывания «промиса»?

«Промисы» обладают интересными свойствами. Обозначим p = какой-либо «промис», id = Promise.resolve.bind(Promise) (типа A => Promise<A>), f, g и h какие-либо функция (типа A => Promise<B>). Тогда выполняется:

p.then(id).then(f) === p.then(f).then(id) === p.then(f)
p.then(f).then(x => g(x).then(h)) === p.then(x => f(x).then(g)).then(h) === p.then(f).then(g).then(h)

p.then(f).then(g) === p.then(x => f(x).then(g))


Для каждой пары Promise<A> и Promise<B> задана операция cвязывания `then`
Для Promise<A> задано тождественное cвязывание `Promise.resolve`

«Промис» не является цепочкой вызовов. Объект «промис» является контейнером над значением, которое может быть доступно в данный конкретный момент времени, или, возможно, будет доступно только в будущем.

#funfact

Категории включают объекты и морфизмы. Последние представляют собой вещи и способы перехода от одной вещи к другой. В асинхронных вычислениях JavaScript'а под такими объектами обычно понимаются «промисы», а под морфизмами — методы then, catch и finally, которые переводят состояние одного «промиса» в состояние другого «промиса».

Вопросы для исследования:

Возможно ли получить мгновенное значение промиса p? Если написать `p.then(console.log);`, а промис ещё не выполнился, то мы ничего не получим. Возможно ли мгновенно узнать состояние промиса?

Возможно ли, чтобы промис разрешился другим промисом? Означает ли это, что не существует схлапывания «промиса»?

Возможна ли передача состояния между промисами? Представте себе контейнер, назовём его Writer, у которого есть лог в который можно писать и передавать его между промисами, а также возвращаемое значение. Как бы вы такое реализовали?
// набросок класса
class Writer extends Promise {
	then() {
	}

	runWriter() {
		return "";
	}

	static tell(log) {
		return new Writer(r => r([null, log]));
	}

	static resolve(value) {
		return new Writer(r => r([value, ""]));
	}
}

// пример тестируемой функции
function half(v) {
	return Writer.tell(`I just halfed ${v}!`).then(_=> v / 2);
}

// тестирование в консоли
// > half(8).then(half);
// < Promise { <resolved>: [2, "I just halfed 8!I just halfed 4!"] }
// > await half(8).then(half);
// < 2
// > half(8).then(half).runWriter();
// < "I just halfed 8!I just halfed 4!"
// >

Последний раз редактировалось Malleys, 05.07.2018 в 11:26.
Ответить с цитированием
  #22 (permalink)  
Старый 05.07.2018, 14:34
Аватар для Alexandroppolus
Профессор
Отправить личное сообщение для Alexandroppolus Посмотреть профиль Найти все сообщения от Alexandroppolus
 
Регистрация: 25.10.2016
Сообщений: 1,005

Сообщение от Malleys
function Success(v) { return Promise.resolve(v); }
function Failure(v) { return Promise.reject (v); }
function Try(fn) {
    return new Promise(r => r(fn()))
        .then(Success)
        .catch(Failure);
}
это вроде бы можно сократить до
function Try(fn) {
    return new Promise(r => r(fn()));
}


Сообщение от Malleys
Возможно ли получить мгновенное значение промиса p? Если написать `p.then(console.log);`, а промис ещё не выполнился, то мы ничего не получим. Возможно ли мгновенно узнать состояние промиса?
в общем случае нельзя.
если это наш промис (созданный через new Promise(...)), то в его функцию можно дописать изменение какого-то внешнего флажка, и смотреть этот флажок. А иначе никак - даже если промис уже зарезолвлен, функция в then пойдет следующим микротаском, т.е. синхронно не определить. Можно только асинхронно выяснить, что на момент начала проверки промис "уже был зарезолвлен"

Сообщение от Malleys
Возможно ли, чтобы промис разрешился другим промисом?
зареджектиться можно, зарезолвиться нельзя, будет вызван его метод then для получения результата. Причем это не только для промиса, а вообще для любого thenable
Ответить с цитированием
  #23 (permalink)  
Старый 05.07.2018, 15:46
Аватар для Aetae
Тлен
Отправить личное сообщение для Aetae Посмотреть профиль Найти все сообщения от Aetae
 
Регистрация: 02.01.2010
Сообщений: 6,492

Во втором случе можно такой хак зафигачить)
var p = new Promise(e=>e(1));
Object.defineProperty(p, 'then', {
  get(){
    delete p.then;
    return; //первый раз возвращаем undefined и обманываем систему :)
  },
  enumerable: true,
  configurable: true
})

new Promise(e=>e(p)).then(res => alert(res instanceof Promise) ); //возвращает промис :)

В первом случае тоже можно свою обёртку над стандартным промисом прилепить, которая state будет запоминать.

Но зачем?)
Вообще, имхо, слишком мудрите вы с этим.
__________________
29375, 35

Последний раз редактировалось Aetae, 05.07.2018 в 15:48.
Ответить с цитированием
  #24 (permalink)  
Старый 05.07.2018, 16:15
Аватар для Alexandroppolus
Профессор
Отправить личное сообщение для Alexandroppolus Посмотреть профиль Найти все сообщения от Alexandroppolus
 
Регистрация: 25.10.2016
Сообщений: 1,005

Сообщение от Aetae
Но зачем?)
сказано же - "Вопросы для исследования"

Сообщение от Aetae
В первом случае тоже можно свою обёртку над стандартным промисом прилепить, которая state будет запоминать.
и как это сделать?
var oldPromise = Promise;
window.Promise = function(f) {
  console.log('promise');
  return new oldPromise(f);
};
в хроме тот же fetch не подхватывает этот новый Promise

проверку можно сделать, но только асинхронно
function getPromiseState(p) {
	var state = 'pending';
	p.then(function() { state = 'resolved'; }, function() { state = 'rejected'; });
	return Promise.resolve().then(function() { return state; });
}

// --------
getPromiseState(promise).then(console.log);
Ответить с цитированием
  #25 (permalink)  
Старый 05.07.2018, 18:16
Аватар для Aetae
Тлен
Отправить личное сообщение для Aetae Посмотреть профиль Найти все сообщения от Aetae
 
Регистрация: 02.01.2010
Сообщений: 6,492

Alexandroppolus, концептуально как-то так:
(function(prototype){
  const then = prototype.then;
  prototype.state = 'pending';
  prototype.then = function(){
    const next = then.apply(
      then.call(
        this,
        resolve => {
          this.state = 'fulfilled';
          return resolve;
        }, 
        reject => {
          this.state = 'rejected';
          throw reject;
        }
      ), 
      arguments
    );
    return next;
  }
}(Promise.prototype));

+для Promise.resolve/reject надо перегрузку, ешё мб что-то с catch и final.
+отдельно обработку для случаев когда promise возвращает не promise(передавать в таких случаях статус с this на next, ибо оный в любом случае не поменяется)
+ ...
__________________
29375, 35

Последний раз редактировалось Aetae, 05.07.2018 в 18:25.
Ответить с цитированием
  #26 (permalink)  
Старый 05.07.2018, 18:43
Аватар для Alexandroppolus
Профессор
Отправить личное сообщение для Alexandroppolus Посмотреть профиль Найти все сообщения от Alexandroppolus
 
Регистрация: 25.10.2016
Сообщений: 1,005

Aetae,
state меняется только после вызова then, и то не сразу
Ответить с цитированием
  #27 (permalink)  
Старый 05.07.2018, 19:20
Аватар для Aetae
Тлен
Отправить личное сообщение для Aetae Посмотреть профиль Найти все сообщения от Aetae
 
Регистрация: 02.01.2010
Сообщений: 6,492

Alexandroppolus, и? Должно быть как-то по другому?)
__________________
29375, 35
Ответить с цитированием
  #28 (permalink)  
Старый 05.07.2018, 19:42
Аватар для Alexandroppolus
Профессор
Отправить личное сообщение для Alexandroppolus Посмотреть профиль Найти все сообщения от Alexandroppolus
 
Регистрация: 25.10.2016
Сообщений: 1,005

Aetae,
ну вот, например, я открыл консоль на этой странице, выполнил твой код, а потом:
> prom = fetch('/')
< Promise {<pending>}

> prom
< Promise {<resolved>: Response}

> prom.state
< "pending"

и немножечко смущает, что вот так. Думаю, Malleys спрашивал про то что в треугольных скобках
Ответить с цитированием
  #29 (permalink)  
Старый 05.07.2018, 23:32
Аватар для Aetae
Тлен
Отправить личное сообщение для Aetae Посмотреть профиль Найти все сообщения от Aetae
 
Регистрация: 02.01.2010
Сообщений: 6,492

Alexandroppolus, ну без then в промисе смысла особого нет.)
А так просто придётся подменять каждую нужную нам нативную фигню возвращающую промис, т.к. все они хранят в себе ссылку на нативный промис, который нам не подменить. Возможно можно как-то поиграться с Symbol.species, но тут уж хз.
__________________
29375, 35
Ответить с цитированием
  #30 (permalink)  
Старый 07.07.2018, 09:27
Аватар для Malleys
Профессор
Отправить личное сообщение для Malleys Посмотреть профиль Найти все сообщения от Malleys
 
Регистрация: 20.12.2009
Сообщений: 1,714

Цитата:
Возможно ли получить мгновенное значение промиса p?
Я такое придумал...
function getPromiseValue(promise) {
    return Promise.race([
        promise, Promise.resolve()
    ]);
}

// > var p = fetch("/");
//   getPromiseValue(p);
// < Promise { <resolved>: undefined }
// > getPromiseValue(p);
// < Promise { <resolved>: Response }
// >
Ответить с цитированием
Ответ



Опции темы Искать в теме
Искать в теме:

Расширенный поиск


Похожие темы
Тема Автор Раздел Ответов Последнее сообщение
Соединение массива Artur_Hopf Общие вопросы Javascript 4 07.06.2018 16:48
Обход многомерного массива с задержкой после каждого цикла Walk Общие вопросы Javascript 2 14.08.2017 16:17
Вывод последовательно итераций массива без перезагрузки lexus777 AJAX и COMET 1 28.03.2016 14:31
Помогите к js коду, написать html код Modrih Элементы интерфейса 8 16.06.2015 18:08
Сортировка массива по возрастанию другого массива. vas88811 Events/DOM/Window 4 12.01.2014 10:31