Pull to refresh
0
0
amr_now @amr_now

User

Send message
Более красивая итоговая реализация:
async function selectPizza() {
    let pizzaData = await getPizzaData();    // асинхронный запрос
    let chosenPizza = choosePizza();    // синхронный вызов
    await addPizzaToCart(chosenPizza);    // асинхронный вызов
}

async function selectDrink() {
    let drinkData = await getDrinkData();    // асинхронный запрос
    let chosenDrink = chooseDrink();    // синхронный вызов
    await addDrinkToCart(chosenDrink);    // асинхронный вызов
}

async function orderItems() {
    let items = await getCartItems();    // Товары в корзине
    let promises = items.map(sendRequest); // Получен массив горячих промисов
    await Promise.whenAll(promises);
}

let promises = [selectPizza(), selectDrink()];
await Promise.whenAll(promises);

await orderItems();   // асинхронный вызов

Исключения обработаем на внешнем уровне.

Полифиллы для AggregateError и Promise.allSettled():
class AggregateError extends Error {
    constructor(errors, message) {
        super(message);
        this.errors = errors && errors[Symbol.iterator] ? [...errors] : [];
    }
}

/**
 * Аналог черновика Promise.allSettled(). Не полностью соответствует Task.WhenAll()
 * Возвращаемый промис будет завершен, когда все из последовательности промисов будут завершены. 
 * Возвращаемый промис всегда будет завершаться в состоянии resolved, в отличие от Task.WhenAll()  
 * Это справедливо, даже если завершенные промисы будут находиться в состоянии rejected.
 * 
 * @returns {Array} Массив объектов {value, reason, status: "fulfilled" или "rejected"}
 * Свойства value или reason могут отсутствовать.
 */
if (!Promise.allSettled) {
    Promise.allSettled = promises => new Promise((resolve, reject) => {
        let inputTasks = Array.from(promises);
        let array = [],
            count = 0,
            len = inputTasks.length;
        for (let i = 0; i < len; ++i) {
            inputTasks[i].then(
                value => {
                    array[i] = { status: "fulfilled", value: value };
                    if (++count === len) resolve(array);
                },
                reason => {
                    array[i] = { status: "rejected", reason: reason };
                    if (++count === len) resolve(array);
                }
            );
        }
    });
}

Для красоты эмулируем Task.WhenAny() как надстройку над штатным методом Promise.allSettled() (можно и под другим именем, но пока не важно):
/**
 * Создать промис, который будет завершен, когда все из последовательности 
 * промисов будут завершены. 
 * Возвращаемый промис будет успешно завершен, если все промисы успешно завершены.
 * Результатом будет массив значений промисов.
 * Возвращаемый промис будет неуспешно завершен, если любой из промисов 
 * завершен неуспешно. 
 * Возвращенной ошибкой будет AggregateError c  массивом ошибок промисов.
 */
Promise.whenAll = promises => new Promise((resolve, reject) => {
    Promise.allSettled(promises).then(
        array => {
            let errors = array
                .filter(e => e.status === "rejected")
                .map(e => e.reason);
            if (errors.length)
                reject(new AggregateError(errors));
            else
                resolve(array.map(e => e.value));
        },
        error => { reject(error); }
    );
});
С учётом ваших замечаний. Прошу потестить.
if (!Promise.allSettled) {
    console.log("Не было Promise.allSettled");

    Promise.allSettled = promises => new Promise((resolve, reject) => {
        let inputTasks = Array.from(promises);
        let array = [],
            count = 0,
            len = inputTasks.length;
        for (let i = 0; i < len; ++i) {
            inputTasks[i].then(
                value => {
                    array[i] = { status: "fulfilled", value: value };
                    if (++count === len) resolve(array);
                },
                reason => {
                    array[i] = { status: "rejected", reason: reason };
                    if (++count === len) resolve(array);
                }
            );
        }
    });
}

/**
 * Возвращаемый промис будет завершен, когда любой из последовательности промисов завершен. 
 * Возвращаемый промис всегда будет завершаться в состоянии resolved. 
 * Это справедливо, даже если первый завершенный промис находится в состоянии rejected.
 * 
 * Поскольку в JavaScript результат промиса не может быть промисом, то 
 * результат возвращенного промиса — массив из одного элемента: первого завершенного промиса.
 */
Promise.whenAny = promises => new Promise((resolve, reject) => {
    for (let promise of promises) {
        let func = () => { resolve([promise]); };
        promise.then(func, func);
    }
});

/*
 * Task.Delay()
 */
Promise.delay = ms => new Promise(resolve => { setTimeout(resolve, ms); });


// ОБРАБОТКА ИСКЛЮЧЕНИЙ.

async function ThrowNotImplementedExceptionAsync() {
    throw new Error("Not implemented");
}
async function ThrowInvalidOperationExceptionAsync() {
    throw new Error("Invalid operation");
}

/**
 * Тестовая функция. Подождать определенное время и вернуть результат.
 * @param {Number} val Возвращаемое число, одновременно являющееся задержкой в секундах.
 * @returns {Promise<Number>} Возвращаемое число, взятое из параметра функции.
 */
async function delayAndReturnAsync(/*int*/ val) {
    await Promise.delay(val * 1000);
    return val;
}

/**
 * Дождаться завершения всех промисов и отдельно перебрать исключения.
 */
async function ObserveAllExceptionsAsync() {
    let task1 = ThrowNotImplementedExceptionAsync();
    let task2 = ThrowInvalidOperationExceptionAsync();
    let array = await Promise.allSettled([task1, task2]);
    let allExceptions = array.filter(e => e.status === "rejected").map(e => e.reason);
    for (let e of allExceptions)
        console.log(e.message);
}
async function Method4Async() {
    await ObserveAllExceptionsAsync();
}

(async function main() {
    await Method4Async();

    console.log(await Promise.allSettled([
        new Promise(r => setTimeout(r, 1000, 1)),
        Promise.reject(2),
    ]));
})();

новый тип ошибки AggregateError

Это старый тип ошибки в C#. Но с ним много возни в коде. Стивен Клири, автор книги «Concurrency in C# Cookbook», не любит этот массив ошибок.
В принципе метод Promise.allSettled() был бы неплохим способом не корячиться с этим массивом ошибок.
Но опять же есть желающие и полностью эмулировать Task.WhenAll().
Виноват. Всё правильно. Я async забыл дописать. Всё работает хорошо и с ошибкой, как надо:
private static async Task<int> ThrowNotImplementedExceptionAsync()
{
    throw new Exception("Ошибка2");
}
Зачем верить на слово? Всё тестируется:
private static Task<int> ThrowNotImplementedExceptionAsync()
{
    throw new Exception("Ошибка2");
}

private static async Task<int> DelayAndReturnAsync(int val)
{
    await Task.Delay(val * 1000);
    return val;
}

private static async Task TestTryCatchAsync2()
{
    var promises = new Task<int>[] {
        DelayAndReturnAsync(1), // Задача без ошибки
        ThrowNotImplementedExceptionAsync() // Задача с ошибкой
    };
    async Task<dynamic> reflect(Task<int> task)
    {
        try
        {
            var result = await task;
            return new { value = result, status = "rejected" };
        }
        catch (Exception e)
        {
            return new { reason = e, status = "rejected" };
        }
    }
    var results = await Task.WhenAll(promises.Select(reflect).ToArray());
    Console.WriteLine(results[0].value);
}

Падение именно при исполнении инструкции объявления массива задач.
Достаточно убрать задачу с ошибкой, и всё выполнится.
Большое спасибо за ценные примеры.
В C# первая инструкция уже падает с ошибкой. Там не допускается запуск задач, завершающихся ошибкой вне try...catch или без ContinueWith().
    const promises = [new Promise(r => setTimeout(r, 1000, 1)), 
ThrowNotImplementedExceptionAsync()];
    const results = await Promise.all(promises.map(Promise.reflect));
    console.log(results);

А JavaScript непонятно почему допускает.
Дурдом на выезде. NodeJS пишет:
Не было Promise.allSettled
(node:26108) UnhandledPromiseRejectionWarning: 2
(node:26108) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
(node:26108) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
[ { status: 'fulfilled', value: 1 },
{ status: 'rejected', reason: 2 } ]
(node:26108) PromiseRejectionHandledWarning: Promise rejection was handled asynchronously (rejection id: 1)

Сначала матерится, потом увидел, что исключение обработано, и заткнулся )))
Мусор в консоли при использовании try..catch оставляет браузер MS Edge.
А например браузер Opera или NodeJS не оставляют мусор.
Получается, если бояться мусора, то невозможно использовать try..catch.
В общем и целом варианты с then() и try..catch эквивалентны. Допустим, в MS Edge мы будем использовать только then().
Тест в NodeJS:
/**
 * Получить информацию о промисе после того, как дождались его завершения.
 * @param {Promise} promise Промис, для которого нужно получить информацию.
 * @returns {Promise<{status:String,value:Any}|{status:String,reason:Error}>} Промис с объектом информации.
 */
Promise.reflect = promise => {
    return promise.then(
        (v) => {
            return { status: 'fulfilled', value: v };
        },
        (error) => {
            return { status: 'rejected', reason: error };
        }
    );
}

if (!Promise.allSettled){
    console.log("Не было Promise.allSettled");

    Promise.allSettled = promises => new Promise(async (resolve, reject) => {
        let array = [];
        for (let promise of promises) {
            array.push(await Promise.reflect(promise));
        }
        resolve(array);
    });

    // Promise.allSettled = promises => new Promise(async (resolve, reject) => {
    //     let array = [];
    //     for (let promise of promises) {
    //         try {
    //             let result = await promise;
    //             array.push({ status: "fulfilled", value: result });
    //         }
    //         catch (ex) {
    //             array.push({ status: "rejected", reason: ex });
    //         }
    //     }
    //     resolve(array);
    // });
}
// 4) ОБРАБОТКА ИСКЛЮЧЕНИЙ.

async function ThrowNotImplementedExceptionAsync() {
    throw new Error("Not implemented");
}
async function ThrowInvalidOperationExceptionAsync() {
    throw new Error("Invalid operation");
}

/**
 * Дождаться завершения всех промисов и отдельно перебрать исключения.
 */
async function ObserveAllExceptionsAsync() {
    let task1 = ThrowNotImplementedExceptionAsync();
    let task2 = ThrowInvalidOperationExceptionAsync();
    let array = await Promise.allSettled([task1, task2]);
    let allExceptions = array.filter(e => e.status === "rejected").map(e => e.reason);
    for (let e of allExceptions)
        console.log(e.message);
}
async function Method4Async() {
    await ObserveAllExceptionsAsync();
}

(async function main() {
    // 4) ОБРАБОТКА ИСКЛЮЧЕНИЙ.
    await Method4Async();
})();
В ES2015 оба эквивалента C#-комбинаторов ущербные.
Promise.raсe: так как в JS нельзя результом промиса делать промис, то в штатной функции невозможно определить, какой конкретно промис завершился.
Обход:
/**
 * Возвращаемый промис будет завершен, когда любой из последовательности промисов завершен. 
 * Возвращаемый промис всегда будет завершаться в состоянии resolved. 
 * Это справедливо, даже если первый завершенный промис находится в состоянии rejected.
 * 
 * Поскольку в JavaScript результат промиса не может быть промисом, то 
 * результат возвращенного промиса — массив из одного элемента: первого завершенного промиса.
 */
Promise.whenAny = promises => new Promise((resolve, reject) => {
    let result;
    for (let promise of promises) {
        if (result) break;
        let func = () => { if (!result) { result = [promise]; resolve(result); } };
        promise.then(func, func);
    }
});

Promise.all: как выше было замечено, слишком рано вылетает на ошибке, поэтому не достигает функциональности Task.WhenAll()
Обход:
/**
 * Аналог черновика Promise.allSettled(). Не полностью соответствует Task.WhenAll()
 * Возвращаемый промис будет завершен, когда все из последовательности промисов будут завершены. 
 * Возвращаемый промис всегда будет завершаться в состоянии resolved, в отличие от Task.WhenAll()  
 * Это справедливо, даже если завершенные промисы будут находиться в состоянии rejected.
 * 
 * @returns {Array} Массив объектов {value, reason, status: "fulfilled" или "rejected"}
 * Свойства value или reason могут отсутствовать.
 */
if (!Promise.allSettled)
    Promise.allSettled = promises => new Promise(async (resolve, reject) => {
        let array = [];
        for (let promise of promises) {
            try {
                let result = await promise;
                array.push({ status: "fulfilled", value: result });
            }
            catch (ex) {
                array.push({ status: "rejected", reason: ex });
            }
        }
        resolve(array);
    });

В сущности прямой эквивалент Task.WhenAll() уже не нужен. Мы можем перебрать исходную коллекцию промисов и оттуда забрать нужные результаты и ошибки.

Можно отметить, что свойства возвращаемого объекта в Promise.allSettled не соответствуют функции reflect() из npm promise-reflect.
Обход:
/**
 * Получить информацию о промисе после того, как дождались его завершения.
 * @param {Promise} promise Промис, для которого нужно получить информацию.
 * @returns {Promise<{status:String,value:Any}|{status:String,reason:Error}>} Промис с объектом информации.
 */
Promise.reflect = promise => {
    return promise.then(
        (v) => {
            return { status: 'fulfilled', value: v };
        },
        (error) => {
            return { status: 'rejected', reason: error };
        }
    );
}

Соответственно, теперь уже будет можно по-человечески реализовать Task Asynchronous Pattern из:
devblogs.microsoft.com/pfxteam/processing-tasks-as-they-complete
codeblog.jonskeet.uk/2012/01/16/eduasync-part-19-ordering-by-completion-ahead-of-time
github.com/StephenCleary/AsyncEx/blob/master/src/Nito.AsyncEx.Tasks/TaskExtensions.cs
В статье есть важная ошибка: «Исполнительная функция выполняется сразу же после создания промиса.»
По спецификации и по факту — исполнительная функция (параметр конструктора) выполняется ПЕРЕД тем, как ссылка на только что созданный экземпляр Promise станет доступна получателю («левой части выражения»).

Ещё интересно, что практически ни один автор не упоминает, что результатом экземпляра Promise никогда не может быть вложенный экземпляр Promise. (В отличие от того же .NET Framework).
Которое стало бессмысленно использовать, так как потерялась сама идея комбайна браузера с почтой.
И даже на работе Linux?

Дома то все извращаются как могут.
Знаю людей, у когорых дома в принципе нет компьютера и телевизора.
В Андроиде обычно есть аппаратная кнопка «Настройки» ниже экрана.
В Опере это полный эквивалент красной буквы О. Рекомендую.
Да-да.
Иконка, скин — Опера останется Оперой. ;D
— Кстати, инишники они давно уже хотели уничтожить. Уже пару лет игрались с новым форматом файлов конфигурации. Так что сомневаюсь, что останутся.
Нужно в opera:config снять галку «Enable NTLM-authorization».
Тогда диалог будет издеваться только один раз при запуске.
IE8 и FF 18. squid, AD и NTLM-авторизация. Логин и пароль никогда не запрашиваются.
> Да и сложно будет делать мобильную версию, не имею десктопной

Золотые слова! Следует, что и на десктоп нужен движок Webkit ))
При запуске оперы не должно ни одного раза запрашиваться пароля, если стоит галка «Сохранить пароль»
> Прокси с авторизацией?

А по картинке не видно? :) У меня например на работе squid с NTLM авторизацией.
Много лет не переставала,
вероятно тенденция сохранится.
1

Information

Rating
Does not participate
Registered
Activity