Шаблон проектирования для управления несколькими асинхронными операциями JavaScript - PullRequest
23 голосов
/ 12 июля 2011

Я ищу хороший шаблон проектирования для отслеживания множества различных асинхронных операций JavaScript (загрузка изображений, множественные вызовы AJAX, последовательные вызовы AJAX и т. Д.), Которые лучше, чем просто множество пользовательских обратных вызовов и настраиваемое состояние переменные. Что бы вы предложили мне использовать? Есть ли какой-либо тип системы очередей с возможностью иметь логику помимо последовательности?

Пример задачи

У меня есть последовательность запуска, включающая ряд асинхронных процессов (загрузка изображений, ожидание таймеров, выполнение некоторых вызовов ajax, выполнение некоторой инициализации). Некоторые асинхронные процессы могут быть запущены для одновременного запуска (загрузка изображений, вызовы AJAX), а некоторые должны быть упорядочены (запустить вызов AJAX № 1, затем вызов AJAX № 2). Прямо сейчас у меня есть все, что запускает функции обратного вызова, и множество глобальных состояний, которые отслеживают, что завершилось или не завершилось. Это работает, но это довольно грязно, и у меня было несколько ошибок из-за сложности проверки правильности обработки всех возможностей секвенирования и ошибок.

Когда у вас есть проблемы, отладка также довольно трудна, потому что это похоже на принцип неопределенности Гейзенберга. Как только вы устанавливаете точку останова в любом месте последовательности, все меняется. Вы должны поместить в код всевозможные операторы отладки, чтобы попытаться понять, что происходит.

Вот более конкретное описание:

  1. Загрузка трех изображений. Как только будет загружено одно конкретное изображение, я хочу его отобразить. Как только оно будет отображаться в течение определенного времени, я хочу отобразить второе изображение. Третий идет в очередь для последующего отображения.

  2. Существует три вызова AJAX, которые должны выполняться в последовательном порядке (вывод одного используется как часть ввода следующего).

  3. Когда AJAX-вызовы выполнены, выполняется обработка результатов JS, а затем необходимо загрузить еще два изображения.

  4. Когда эти два изображения загружены, есть еще кое-что для отображения.

  5. Когда все это сделано, вы проверяете, как долго было показано одно из изображений, и, если прошло достаточно времени, отобразите следующее изображение. Если нет, подождите еще немного, прежде чем отобразить следующее изображение.

У каждого шага есть обработчики успеха и ошибок. Некоторые из обработчиков ошибок запускают альтернативный код, который все еще может успешно продолжаться.

Я не ожидаю, что кто-то будет следовать точному процессу здесь, но просто чтобы дать людям представление о типе логики между шагами.

Редактировать: я столкнулся с AsyncQueue YUI , который не является полным решением для типа проблемы, с которой я столкнулся, но находится в том же месте. Кажется, это больше подходит для упорядочения или упорядочивания множества асинхронных операций, но я не понимаю, как это помогает с тем типом принятия решений, который у меня есть.

Ответы [ 3 ]

11 голосов
/ 12 июля 2011

Взгляните на концепцию Обещания / A . jQuery реализует это с помощью объекта jQuery.Deferred.

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

4 голосов
/ 19 января 2016

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

Решение начинается с выполнения каждой асинхронной операции исоздание оболочки, которая возвращает обещание:

для загрузки изображения:

function loadImage(url) {
    return new Promise(function(resolve, reject) {
        var img = new Image();
        img.onload = function() {
            resolve(img);
        };
        img.onerror = img.onabort = function() {
            reject(url);
        };
        img.src = url;
    });
}

для выполнения вызова Ajax (упрощенная версия):

function ajaxGet(url) {
    return new Promise(function(resolve, reject) {
        var req = new XMLHttpRequest();
        req.addEventListener("load", resolve);
        req.addEventListener("error", reject);
        req.addEventListener("abort", reject);
        req.open("GET", url);
        req.send();
    });
}

для ожиданияЗа определенное время до выполнения операции вы даже можете создать версию обещания setTimeout(), чтобы она могла быть связана с другими операциями обещания:

// delay, return a promise
// val is optional
function delay(t, val) {
    return new Promise(function(resolve) {
        setTimeout(function() {
            resolve(val);
        }, t);
    });
}

Теперь их можно объединить для создания логикизадаваемый вопрос:

Загрузка трех изображений.Как только будет загружено одно конкретное изображение, я хочу его отобразить.Как только оно будет отображаться в течение определенного времени, я хочу отобразить второе изображение.Третий помещается в очередь для последующего отображения.

// start all three images loading in parallel, get a promise for each one
var imagePromises = [url1, url2, url3].map(function(item) {
    return loadImage(item);
});

// display the three images in sequence with a delay between them
imagePromises.reduce(function(p, item) {
    return p.then(function() {
        // when the next image is ready display it
        return item.then(function(img) {
            displayImage(img);
            return delay(15 * 1000);
        });
    });
}, Promise.resolve());

При использовании .reduce() показан классический шаблон проектирования для последовательности последовательности операций над массивом с использованием обещаний.

Существует три вызова AJAX, которые должны выполняться последовательно (выход одного используется как часть ввода следующего).

и

Когда AJAX-вызовы завершены, выполняется обработка результатов JS, а затем необходимо загрузить еще два изображения.

var p = ajaxGet(url1).then(function(results1) {
    // do something with results1
    return ajaxGet(url2);
}).then(function(results2) {
    // do something with results2
    return ajaxGet(url3); 
}).then(function(results3) {
    // process final results3 here
    // now load two images and when they are loaded do some display stuff
    return Promise.all(loadImage(imgx), loadImage(imgy)).then(function(imgs) {
        doSomeDisplayStuff(imgs);
    });
});
0 голосов
/ 04 апреля 2015

Хотя Обещания дают нам более чистый код, который мы можем улучшить с помощью Генераторов . Я написал пост о том, как использовать Генераторы в Tame Async JavaScript с ES6 . Использование описанного шаблона облегчит работу со сложными асинхронными взаимодействиями и создаст ментальную модель для await возможностей, запланированных для ES7.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...