Tudo sobre promessas (para aplicativos da Windows Store escritos em JavaScript).

Ao imprimir aplicativos da Windows Store no JavaScript, você encontra construções chamadas de promessas assim que você faz algo que envolva uma API assíncrona. E também não leva muito tempo para que cadeias de promessa de gravação para operações assíncronas sequenciais se tornem algo como uma segunda pele.

No entanto, durante seu trabalho de desenvolvimento, você provavelmente encontrará outros usos de promessas em que não ficará totalmente claro o que está acontecendo. Um bom exemplo disso é a otimização de funções de renderização de item para o controle de um ListView como mostrado na amostra de desempenho de otimização do ListView em HTML. Exploraremos isso em uma postagem subsequente. Ou considere essa joia que Josh Williams mostrou em sua palestra Deep Dive into WinJS em //build 2012 (ligeiramente modificada):

 list.reduce(function callback (prev, item, i) {
    var result = doOperationAsync(item);
    return WinJS.Promise.join({ prev: prev, result: result}).then(function (v) {
        console.log(i + ", item: " + item+ ", " + v.result);
    });
})

Este trecho reúne as promessas de operações assíncronas paralelas e fornece seus resultados sequencialmente, de acordo com a ordem na lista. Se você puder olhar para esse código e entender imediatamente sua função, fique à vontade para ignorar esta postagem! Caso contrário, vamos observar melhor o funcionamento de promessas e sua expressão no WinJS (a Biblioteca do Windows para JavaScript) para que possamos compreender esses tipos de padrão.

O que é uma promessa? Os relacionamentos entre promessas

Vamos começar por uma verdade fundamental: uma promessa nada mais é do que uma construção de código ou, se você achar melhor, uma convenção de chamadas de função. Como tal, as promessas não têm relacionamentos inerentes com as operações assíncronas, elas só são muito úteis nesse aspecto! Uma promessa é simplesmente um objeto que representa um valor que pode estar disponível em algum momento no futuro, se já não estiver. Dessa forma, uma promessa tem o mesmo significado do uso da palavra em relacionamentos humanos. Se eu disser, “Prometo te dar uma dúzia de donuts”, com certeza não preciso ter esses donuts no momento, mas certamente suponho que terei em algum momento no futuro. E quando tiver, vou te dar.

Portanto, uma promessa indica um relacionamento entre dois agentes: a origem que faz a promessa de fornecer algumas mercadorias e o consumidor, que é o destinatário dessa promessa e das mercadorias. A maneira como obterá essas mercadorias é de responsabilidade da origem. Da mesma maneira, o consumidor pode fazer o que quiser com a própria promessa e as mercadorias fornecidas. Ele pode até compartilhar a promessa com outros consumidores.

Entre a origem e o consumidor também há dois estágios desse relacionamento: criação e cumprimento. Tudo isso é mostrado no diagrama a seguir.

promise_diagram

Esses dois estágios do relacionamento são o motivo pelo qual as promessas funcionam bem com o fornecimento assíncrono, como podemos ver se seguirmos o fluxo do diagrama. A parte principal é que uma vez que o consumidor obtiver a confirmação da solicitação, a promessa, ele poderá seguir sua vida (de forma assíncrona) em vez de esperar (de forma síncrona). Isso significa que o consumidor pode realizar outras tarefas enquanto espera a promessa ser cumprida, como responder a outras solicitações, que é o principal objetivo das APIs assíncronas. E se as mercadorias já estiverem disponíveis? Então a promessa será imediatamente cumprida, fazendo com que todo o processo se torne um tipo de convenção de chamadas de função.

Obviamente, esse relacionamento é composto por outros elementos que devemos considerar. Com certeza você já fez promessas na sua vida e já fizeram promessas para você também. Embora muitas dessas promessas tenham sido cumpridas, a realidade é que muitas promessas não são. É possível que o entregador de pizza sofra um acidente no caminho para a sua casa! Promessas que não são cumpridas são mais um fato da vida e temos que aceitar isso em nossas vidas pessoais e de programação assíncrona.

No relacionamento de promessas, portanto, isso significa que a origem precisa de uma forma de dizer “desculpe, mas não vou conseguir cumprir essa promessa” e o cliente precisa de uma maneira de saber quando esse for o caso. Em segundo lugar, como consumidores podemos, às vezes, ser um pouco impacientes com promessas feitas para nós. Portanto, se a origem puder controlar seu progresso no cumprimento da promessa, o consumidor também precisará de uma maneira de receber essas informações. Em terceiro lugar, o consumidor também pode cancelar o pedido e dizer para a origem que não precisa mais da mercadoria.

Adicionando esses requisitos ao diagrama, podemos ver o relacionamento completo:

promise_diagram_2

Vamos ver como esses relacionamentos são manifestados em códigos.

A construção da promessa e cadeias de promessa

Na verdade, existem várias propostas ou especificações diferentes para promessas. As usadas no Windows e WinJS são chamadas de Common JS/Promessas A, que dizem que uma promessa (o que é retornado por uma origem para representar um valor a ser fornecido no futuro) é um objeto com uma função chamada de then. Os consumidores se inscrevem para o cumprimento da promessa chamando then. (As promessas no Windows também dão suporte a uma função similar chamada de done usada em cadeias de promessas, como veremos em breve.)

Para essa função, o consumidor passa até três funções opcionais como argumentos, nesta ordem:

  1. Um manipulador completo. A origem chama esta função quando o valor da promessa está disponível e se esse valor já estiver disponível, o manipulador completo será chamado imediatamente (sincronamente) de dentro da função then.
  2. Um manipulador de erros opcional chamado em caso de falhas para adquirir o valor prometido. Para qualquer promessa, o manipulador completo nunca será chamado se o manipulador de erros tiver sido chamado.
  3. Um manipulador de progresso opcional chamado periodicamente com resultados intermediários, se a operação der suporte. (No WinRT, isso significa que a API tem um valor de retorno de IAsync[Action | Operation]WithProgress; aquelas com IAsync[Action | Operation] são diferentes.)

Observe que você pode passar como nulo para qualquer um desses argumentos, como quando você quiser anexar somente um manipulador de erros e não um manipulador completo.

Em outro aspecto do relacionamento, um consumidor pode inscrever quantos manipuladores quiser para a mesma promessa chamando then várias vezes. Ele também pode compartilhar a promessa com outros consumidores que também podem chamar then quando quiserem. Isso é totalmente permitido.

Isso significa que uma promessa precisa gerenciar listas dos manipuladores recebidos e invocá-los nos momentos apropriados. As promessas também precisam permitir o cancelamento, como descrito no relacionamento completo.

O outro requisito da especificação Promessas A é que o próprio método then retorna uma promessa. A segunda promessa é cumprida quando o manipulador completo dado para o primeiro promise.then é retornado, com o valor de retorno fornecido como resultado dessa segunda promessa. Considere este trecho do código:

 var promise1 = someOperationAsync();
var promise2 = promise1.then(function completedHandler1 (result1) { return 7103; } );
promise2.then(function completedHandler2 (result2) { });

A cadeia de execução aqui é o início de someOperationAsync, retornando a promise1. Enquanto a operação estiver em andamento, chamaremos a promise1.then, que retornará a promise2 imediatamente. Esteja ciente de que o completedHandler1 não será chamado a menos que o resultado da operação assíncrona já esteja disponível. Vamos supor que ainda estamos esperando, então seguimos diretamente para a chamada da promise2.then e, novamente, o completedHandler2 não será chamado nesse momento.

Algum tempo depois, a someOperationAsync será concluída com um valor de, digamos, 14618. A promise1 já foi cumprida, portanto, chamará o completedHandler1 com esse valor, de forma que o result1 seja 14618. Agora, o completedHandler1 será executado, retornando o valor 7103. Neste momento, a promise2 é cumprida, portanto, chama o completedHandler2 com result2 equivalente a 7103.

E se um manipulador completo retornar outra promessa? Esse caso é resolvido de forma diferente. Digamos que o completedHandler1 do código acima retorne esta promessa:

 var promise2 = promise1.then(function completedHandler1 (result1) {
    var promise2a = anotherOperationAsync();
    return promise2a;
});

Nesse caso, o result2 no completedHandler2 não será a própria promise2a, mas o valor de cumprimento da promise2a. Ou seja, como o manipulador completo retorna uma promessa, a promise2 como retornada da promise1.then, será cumprida com os resultados da promise2a.

Essa característica é exatamente o que torna possível o encadeamento de operações assíncronas sequenciais, em que os resultados de cada operação na cadeia é alimentado na próxima. Sem variáveis intermediárias ou manipuladores nomeados, normalmente, você encontra esse padrão para cadeias de promessas:

 operation1().then(function (result1) {
    return operation2(result1)
}).then(function (result2) {
    return operation3(result2);
}).then(function (result3) {
    return operation4(result3);
}).then(function (result4) {
    return operation5(result4)
}).then(function (result5) {
    //And so on
});

Cada manipulador completo, claro, provavelmente fará mais com os resultados recebidos, mas essa estrutura principal é comum a todas as cadeias. O que também acontece é que todos os métodos then aqui são executados em sequência, já que tudo que estão fazendo é economizar o manipulador completo e retornar outra promessa. Portanto, até chegarmos ao final desse código, somente a operation1 terá sido iniciada e nenhum manipulador completo terá sido chamado. Mas várias promessas intermediárias de todas as chamadas then foram criadas e ligadas umas as outras para gerenciar a cadeia como o progresso de operações sequenciais.

Vale a pena observar que a mesma sequência pode ser obtida pelo aninhamento de cada operação subsequente dentro do manipulador completo. Nesse caso, você não terá todas as declarações de returno. No entanto, esses aninhamentos se tornam um pesadelo de recuo, principalmente se você começar a adicionar manipuladores de progresso e erros a cada chamada para then.

Falando nisso, um dos recursos das promessas no WinJS é que os erros que ocorrem em qualquer parte da cadeia são automaticamente para o final da cadeia. Isso significa que você pode simplesmente anexar um único manipulador de erros na última chamada para then em vez de ter manipuladores em todos os níveis. O problema, no entanto, é que por vários motivos sutis, esses erros são engolidos se o último link na cadeia for uma chamada para then. Por isso, o WinJS fornece um método done nas promessas. Este método aceita os mesmos argumentos de then, mas indica que a cadeia está completa (ele retorna indefinido em vez de outra promessa). Qualquer manipulador de erros anexado ao done será chamado em caso de erros em toda a cadeia. Além disso, sem um manipulador de erros, o done enviará uma exceção para o nível do aplicativo, onde poderá ser manipulado pelo window.onerror de eventos WinJS.Application.onerror. Resumindo, todas as cadeiras deveriam terminar, idealmente, em done para garantir que exceções sejam exibidas e manipuladas corretamente.

É claro que se você escrever uma função com objetivo de retornar a última promessa de uma longa cadeia de chamadas then, você ainda usará then ao final: a responsabilidade de manipulação de erros, então, pertence ao chamados que pode usar essa promessa em outra cadeia totalmente diferente.

Criação de promessas: a classe WinJS.Promise

Embora você sempre possa criar suas próprias cadeias de promessa com base na especificação Promessas A, na verdade, isso é muito trabalhoso e pode ser realizado melhor por uma biblioteca. Por isso, o WinJS fornece uma classe de promessas eficiente, bem testadas e flexíveis chamada de WinJS.Promise. Isso permite que você crie facilmente promessas com base em valores e operações diferentes sem precisar gerenciar os detalhes dos relacionamentos de origem/consumidor ou o comportamento de then.

Quando necessário, você pode (e deve) usar o novo WinJS.Promise, ou uma função de ajuda adequada, como observado na próxima seção, para criar promessas com base em operações e valores existentes (síncronos) da mesma maneira. Lembre-se de que uma promessa é apenas uma construção de código: não há requisitos que exijam que uma promessa tenha que englobar uma operação assíncrona ou qualquer outro aspecto assíncrono. Da mesma maneira, o simples ato de englobar alguma parte de código em uma promessa não faz com que ele seja executado assincronamente . Você ainda precisa fazer esse trabalho.

Como um exemplo simples do uso direto de WinJS.Promise, digamos que queremos executar uma longa computação (adição de vários números de um para algum valor máximo), mas de forma assíncrona. Poderíamos inventar nosso próprio mecanismo de retorno de chamada para essa rotina, mas se englobarmos isso dentro de uma promessa, permitiremos que ela seja encadeada ou combinada a outras promessas de outras APIs. (Parecido com isso, a função WinJS.xhr engloba a XmlHttpRequest assíncrona do JavaScript dentro de uma promessa para que você não precise lidar com a estrutura de eventos dela.)

Também podemos usar um trabalho do JavaScript para uma computação longa, é claro, mas devido à ilustração, vamos mantê-lo no thread da interface e usar o setImmediate para dividir a operação em etapas. Veja como podemos implementá-lo em uma estrutura de promessas usando WinJS.Promise:

 function calculateIntegerSum(max, step) {
    //The WinJS.Promise constructor's argument is an initializer function that receives 
    //dispatchers for completed, error, and progress cases.
    return new WinJS.Promise(function (completeDispatch, errorDispatch, progressDispatch) {
        var sum = 0;

        function iterate(args) {
            for (var i = args.start; i < args.end; i++) {
                sum += i;
            };

            if (i >= max) {
                //Complete--dispatch results to completed handlers
                completeDispatch(sum);
            } else {
                //Dispatch intermediate results to progress handlers
                progressDispatch(sum);
                setImmediate(iterate, { start: args.end, end: Math.min(args.end + step, max) });
            }
        }
            
        setImmediate(iterate, { start: 0, end: Math.min(step, max) });
    });
}

Ao chamar o novo WinJS.Promise, o único argumento para sua construção em uma função de inicializador (nesse caso, anônimo). O inicializador encapsula a operação a ser executada, mas esteja ciente de que esta função sozinha é executada de forma síncrona no thread de interface. Então se executarmos um longo cálculo aqui sem usar o setImmediate, bloquearíamos o thread de interface para todo esse tempo. Novamente, a colocação de um código dentro de uma promessa nãofaz com que ela seja automaticamente síncrona, a função de inicializador precisa configurar isso.

Para argumentos, a função de inicializador recebe três distribuidores para os casos completos, de erro e progresso que recebem suporte das promessas. Como você pode ver, chamamos esses distribuidores em momentos apropriados durante a operação com os argumentos apropriados.

Chamo eles de “distribuidores” de função, porque eles não são iguais aos manipuladores que os consumidores inscrevem no método then da promessa (ou done, mas já falei sobre isso). Nos bastidores, o WinJS está gerenciando matrizes desses manipuladores, que é o que permite que qualquer número de consumidores se inscreva em qualquer número de manipuladores. Quando você invoca um desses distribuidores, o WinJS reitera por meio de sua lista interna e invoca todos esses manipuladores em seu nome. O WinJS.Promise também garante que seu then retorne outra promessa, como exigido para o encadeamento.

Resumindo, o WinJS.Promise fornece todos os detalhes ao redor de uma promessa. Isso permite que você se concentre na operação principal representada pela promessa, que é incorporada na função do inicializador.

Ajuda na criação de promessas

A função principal da ajuda para criar uma promessa é o método estático WinJS.Promise.as, que engloba qualquer valor em uma promessa. Esse wrapper em um valor já existente simplesmente volta ao início e chama qualquer manipulador completo para then. Especificamente, isso permite que você trate valores conhecidos arbitrários como promessas de tal forma que você possa fazer misturas e composições (juntando ou encadeando) com outras promessas. Usando da mesma forma que uma promessa existente apenas retorna essa promessa.

A outra função de ajuda estática é WinJS.Promise.timeout, que fornece um wrapper conveniente com base em setTimeout e setImmediate. Você também pode criar uma promessa que cancele uma segunda promessa se essa segunda não tiver sido cumprida em um determinado número de milissegundos.

Observe que as promessas tempo de espera com base em setTimeout e setImmediate são cumpridas com indefinido. Uma pergunta comum é “como elas podem ser usadas para fornecer um resultado diferente após o tempo de espera?” A resposta usa o fato de que then retorna outra promessa cumprida com o valor de retorno do manipulador completo. Esta linha de código, por exemplo:

 var p = WinJS.Promise.timeout(1000).then(function () { return 12345; });

cria a promessa p que será cumprida com o valor 12345 depois de um segundo. Em outras palavras, WinJS.Promise.timeout(…).then(function () { return <value>} ) é um padrão para fornecer <value> após o determinado tempo de espera. E se o próprio <value> for outra promessa, isso significa fornecer o valor de cumprimento dessa promessa em algum momento após o tempo de espera.

Cancelamento e geração de erros de promessa

No código que acabamos de ver, você deve ter percebido duas deficiências. A primeira é que não há como cancelar a operação depois que ela é iniciada. A segunda é que não fazemos um trabalho muito bom lidando com erros.

O truque nesses dois casos são funções de produção de promessa, como a função calculateIntegerSum que deve sempre retornar uma promessa. Se uma operação não for concluída ou não for iniciada em primeiro lugar, essa promessa fica no que chamamos de estado de erro. Isso significa que a promessa não tem e nunca terá um resultado que possa passar para qualquer manipulador completo: só sempre chamará seus manipuladores de erros. De fato, se um consumidor chamar then em uma promessa, que já está no estado de erro, a promessa invocará imediatamente (de forma síncrona) o manipulador de erro fornecido para then.

Um WinJS.Promise insere o estado de erro por dois motivos: o consumidor chama seu método cancelar ou o código na função de inicializador chama o distribuidor de erros. Quando isso acontece, os manipuladores de erro recebem qualquer valor de erro que tenha sido pego ou propagado na promessa. Se você estiver criando uma operação em uma WinJS.Promise, também pode usar uma instância de WinJS.ErrorFromName. Esse é apenas um objeto do JavaScript que contém uma propriedade name que identifica o erro e uma propriedade message com mais informações. Por exemplo, quando uma promessa é cancelada, os manipuladores de erros recebem um objeto de erro com nome name e message configurado para “Cancelado”.

Mas e se você não conseguir nem iniciar a operação? Por exemplo, se você chamar calculateIntegerSum com argumentos ruins (como 0, 0), ele não deve nem tentar iniciar a contagem e, em vez disso, deve retornar a promessa no estado de erro. Esse é o objetivo do método estático WinJS.Promise.wrapError. Isso exige uma instância de WinJS.ErrorFromName e retorna uma promessa no estado de erro, que retornaríamos nesse caso, em vez de uma nova instância WinJS.Promise.

O outro lado disso tudo é que embora uma chamada para o método cancelar da promessa coloque-a no estado de erro, como interrompemos a operação assíncrona que está em andamento? Na implementação de calculateIntegerSum anterior, ele continuará chamando setImmediate até que a operação seja concluída, independentemente do estado da promessa criada. Na verdade, se a operação chamar o distribuidor completo depois que a promessa tiver sido cancelada, ela ignorará essa conclusão.

O necessário, então, é uma maneira de a promessa dizer para a operação que não precisa mais continuar seu trabalho. Por isso, a construção WinJS.Promise precisa de um segundo argumento de função que será chamado se a promessa for cancelada. Nesse exemplo, uma chamada para essa função precisaria evitar a próxima chamada para setImmediate, interrompendo a computação. Veja como isso ocorre, juntamente com a manipulação de erro adequada:

 function calculateIntegerSum(max, step) {
    //Return a promise in the error state for bad arguments
    if (max < 1 || step < 1) {
        var err = new WinJS.ErrorFromName("calculateIntegerSum", "max and step must be 1 or greater");
        return WinJS.Promise.wrapError(err);
    }

    var _cancel = false;

    //The WinJS.Promise constructor's argument is an initializer function that receives 
    //dispatchers for completed, error, and progress cases.
    return new WinJS.Promise(function (completeDispatch, errorDispatch, progressDispatch) {
        var sum = 0;

        function iterate(args) {
            for (var i = args.start; i < args.end; i++) {
                sum += i;
            };

            //If for some reason there was an error, create the error with WinJS.ErrorFromName
            //and pass to errorDispatch
            if (false /* replace with any necessary error check -- we don’t have any here */) {
                errorDispatch(new WinJS.ErrorFromName("calculateIntegerSum (scenario 7)", "error occurred"));
            }

            if (i >= max) {
                //Complete--dispatch results to completed handlers
                completeDispatch(sum); 
            } else {
                //Dispatch intermediate results to progress handlers
                progressDispatch(sum);

                //Interrupt the operation if canceled
                if (!_cancel) {
                    setImmediate(iterate, { start: args.end, end: Math.min(args.end + step, max) });
                }
            }
        }
            
        setImmediate(iterate, { start: 0, end: Math.min(step, max) });
    },
    //Cancellation function for the WinJS.Promise constructor
    function () {
        _cancel = true;
    });
}

De forma geral, a criação de instâncias de WinJS.Promise tem muitas utilidades. Por exemplo, se você tiver uma biblioteca que interaja com um serviço da Web por meio de outro método assíncrono, você pode englobar essas operações dentro de promessas. Você também pode usar uma nova promessa para combinar várias operações assíncronas (ou outras promessas!) de fontes diferentes em uma única promessa onde você queira controlar todos os relacionamentos envolvidos. Dentro do código de um inicializador de WinJS.Promise, você pode ter seus próprios manipuladores para outras operações assíncronas e suas promessas. Você pode usá-los para encapsular mecanismos de nova tentativa automáticos para tempos de espera e rede e afins, depender de uma interface de atualizador de progresso genérico ou adicionar registros ou análises dos bastidores. De todas essas formas, o resto do código nunca precisa saber os detalhes e pode lidar apenas com promessas do lado do cliente.

Parecido com isso, é relativamente direto encapsular um trabalho do JavaScript em uma promessa de forma que ela tenha a aparência e se comporte como outras operações assíncronas no WinRT. Os trabalhos, como você deve saber, fornecem seus resultados por meio de uma chamada de postMessage que cria um evento message no objeto de trabalho do aplicativo. Portanto, o código a seguir vincula esse evento a uma promessa que será cumprida com os resultados fornecidos nessa mensagem:

 // This is the function variable we're wiring up.
var workerCompleteDispatch = null;

var promiseJS = new WinJS.Promise(function (completeDispatch, errorDispatch, progressDispatch) {
    workerCompleteDispatch = completeDispatch;
});

// Worker is created here and stored in the 'worker' variable

// Listen for worker events
worker.onmessage = function (e) {
    if (workerCompleteDispatch != null) {
        workerCompleteDispatch(e.data.results); /* event args depends on the worker */
    }
}

promiseJS.done(function (result) {
    // Output for JS worker
});

Para expandir esse código para lidar com erros do trabalho, você precisaria salvar o distribuidor do erro em outra variável, fazer com que o manipulador de eventos da mensagem busque por informações do erro em seus argumentos de evento e chamar o distribuidor de erros em vez do distribuidor completo, como seria apropriado.

Combinação de promessas paralelas

Como as promessas normalmente são usadas para englobar operações assíncronas, é totalmente possível ter várias operações ocorrendo em paralelo. Nesses casos, talvez você queira saber quando um grupo ou uma promessa é cumprida ou quando todas as promessas no grupo são cumpridas. As funções estáticas WinJS.Promise.any e WinJS.Promise.join fornecem isso.

Ambas as funções aceitam uma variedade de valores ou um objeto com propriedades de valores. Esses valores podem ser promessas, e todos os valores que não são de promessas são englobados com WinJS.Promise.as, de forma que toda a matriz ou objeto é composto de promessas.

Veja as características de any:

  • any cria uma única promessa que é cumprida quando uma das outras é cumprida ou quando ocorrem falhas com um erro (um OU lógico). Em essência, o anyanexa manipuladores completos a todas essas promessas, e assim que um manipulador completo é chamado, ele chama todos os manipuladores completos que a própria promessa de anyrecebeu.
  • Depois que a promessa de anytiver sido cumprida (ou seja, depois que a primeira promessa na lista tiver sido cumprida), as outras operações na lista continuarão sendo executadas, chamando quaisquer manipuladores completos, de erros ou progresso atribuídos a essas promessas individualmente.
  • Se você cancelar a promessa de any, todas as promessas na lista serão canceladas.

Já no caso de join:

  • join cria uma única promessa que é cumprida quando todas as outras são cumpridas ou quando ocorrem falhas com um erro (um E lógico). Em essência, joinanexa manipuladores completos e de erros a todas essas promessas e aguarda até que eles sejam chamados antes de chamar todos os manipuladores completos que ele mesmo recebe.
  • A promessa de join também relata progressos para todos os manipuladores de progresso fornecidos. O resultado intermediário nesse caso é uma gama de resultados dessas promessas individuais que já foram cumpridas até o momento.
  • Se você cancelar a promessa de join, todas as outras promessas ainda pendentes serão canceladas.

Além de any e join, existem dois outros métodos estáticos do WinJS.Promise a serem considerados que podem ser úteis:

  • is determina se um valor arbitrário é uma promessa, retornando um booliano. Ele basicamente garante que seja um objeto com uma função chamada de “then”; não faz testes para “done”.
  • theneachaplica manipuladores completos, de erros e progresso a um grupo de promessas (usando then), retornando os resultados como outro grupo de valores dentro de uma promessa. Todos os manipuladores podem ser nulos.

Promessas paralelas com resultados sequenciais

Com WinJS.Promise.join e WinJS.Promise.any, temos a capacidade de trabalhar com promessas paralelas, isto é, com operações assíncronas paralelas. A promessa retornada por join, novamente, são cumpridas quando todas as promessas em uma matriz são cumpridas. No entanto, essas promessas provavelmente serão concluídas de forma aleatória. E se você tiver um conjunto de operações que possam ser executadas dessa maneira, mas quer processar seus resultados de forma bem definida, isto é, a ordem em que suas promessas são exibidas em uma matriz?

Para fazer isso, você precisa combinar todas as promessas subsequentes ao join de todas as anteriores, e a parte do código com que iniciamos esta postagem faz exatamente isso. Veja o código novamente, mas reescrito para explicitar as promessas. (Suponha que a listaseja uma matriz de valores de algum tipo usada como argumentos para uma chamada assíncrona de produção de promessas hipotética doOperationAsync):

 list.reduce(function callback (prev, item, i) {
    var opPromise = doOperationAsync(item);
    var join = WinJS.Promise.join({ prev: prev, result: opPromise});

    return join.then(function completed (v) {
        console.log(i + ", item: " + item+ ", " + v.result);
    });
})

Para compreender este código, primeiro precisamos entender como o método reduce da matriz funciona. Para cada item na matriz, reduce chama o argumento da função, chamado aqui de callback, que recebe quatro argumentos (e apenas três deles são usados no código):

  • prev O valor retornado da chamada previous para callback (este é nulo para o primeiro item).
  • item O valor atual da matriz.
  • i O índice de itens na lista.
  • source A matriz original.

Para o primeiro item da lista, obteremos uma promessa que chamarei de opPromise1. Como o anterior ainda está nulo, combinaremos [WinJS.Promise.as(null), opPromise1] . Mas observe que não retornaremos join. Em vez disso, anexaremos um manipulador completo (que chamei de completo) à este join e ele retornará a promessa de seu then.

Lembre-se de que a promessa retornada de then será cumprida quando o manipulador completo retornar. Isso significa que estamos retornando de callback uma promessa que não será concluída até que o manipulador completo do primeiro item tenha processado os resultados de opPromise1. E se você observar o resultado de uma combinação, ele terá sido cumprido com um objeto que contém os resultados de promessas da lista original. Isso significa que o valor de cumprimento v conterá uma propriedade prev e uma propriedade result, em que a última é o resultado de opPromise1.

Com o próximo item na lista, callback recebe um prev que contém uma promessa do join.then anterior. Depois disso, criaremos uma combinação de opPromise1.then com opPromise2. Como resultado, essa combinação não será concluída até que as duas opPromise2 tenham sido cumpridas e o manipulador completo de opPromise1 tenha sido retornado. Pronto! O manipulador completo2 que anexaremos agora a esta combinação não será chamado até que completed1 tenha sido retornado.

As mesmas dependências continuam sendo criadas para cada item na lista, a promessa de join.then para o item n não será cumprida até que o completedn** seja retornado. Isso garante que os manipuladores completos sejam chamados na mesma sequência que a lista.

Resumo

Nesta postagem vimos que as promessas são apenas uma construção de código ou convenção de chamadas de função, embora muito eficiente, para representar um relacionamento específico entre uma origem, que tem os valores a serem fornecidos em um momento posterior arbitrário e um consumidor que precisa saber quando esses valores estarão disponíveis. Como tal, as promessas trabalham muito bem para representar resultados de operações assíncronas e são usadas extensivamente em aplicativos da Windows Store escritos em JavaScript. A especificações de promessas também permite que operações assíncronas sejam encadeadas juntas, com cada resultado intermediário fluindo de um link para o próximo.

A Biblioteca do Windows para JavaScript, WinJS, fornece uma implementação eficiente de promessas que você pode usar para englobar qualquer tipo de operação. Ela também fornece ajuda para cenários comuns, como a combinação de promessas para operações paralelas. Com elas, o WinJS possibilita o trabalho com operações assíncronas de forma eficiente e eficaz.

Kraig Brockschmidt

Gerente de programas, equipe de ecossistema do Windows

Autor, Programming Windows 8 Apps in HTML, CSS, and JavaScript (Programando aplicativos do Windows 8 em HTML, CSS e JavaScript)