Knex: As promessas não se comportam como esperado nas migrações

Criado em 16 jun. 2014  ·  10Comentários  ·  Fonte: knex/knex

(Pode ser uma duplicata de # 312)

Um arquivo de migração como este:

exports.up = function(knex, Promise) {
  var first = knex.schema.createTable('first', function(table) {
    table.increments('id');
    table.string('name');
  });

  var second = first.then(function() {
    return knex.schema.createTable('second', function(table) {
      table.increments('id');
      table.string('name');
    });
  });

  return Promise.all([first, second]);
};

exports.down = function(knex, Promise) {

};

dá a seguinte saída:

{ __cid: '__cid1',
  sql: 'create table "first" ("id" serial primary key, "name" varchar(255))',
  bindings: [] }
{ __cid: '__cid2',
  sql: 'create table "first" ("id" serial primary key, "name" varchar(255))',
  bindings: [] }
{ __cid: '__cid3',
  sql: 'create table "second" ("id" serial primary key, "name" varchar(255))',
  bindings: [] }
error: duplicate key value violates unique constraint "pg_type_typname_nsp_index"
    at Connection.parseE (/home/sohum/node_modules/pg/lib/connection.js:526:11)
    at Connection.parseMessage (/home/sohum/node_modules/pg/lib/connection.js:356:17)
    at Socket.<anonymous> (/home/sohum/node_modules/pg/lib/connection.js:105:22)
    at Socket.EventEmitter.emit (events.js:95:17)
    at Socket.<anonymous> (_stream_readable.js:745:14)
    at Socket.EventEmitter.emit (events.js:92:17)
    at emitReadable_ (_stream_readable.js:407:10)
    at emitReadable (_stream_readable.js:403:5)
    at readableAddChunk (_stream_readable.js:165:9)
    at Socket.Readable.push (_stream_readable.js:127:10)
question

Comentários muito úteis

Portanto, a confusão aqui é que o método createTable não retorna uma promessa, mas sim um objeto SchemaBuilder , que é um "thenable", ou seja, chamando .then no objeto retornará uma promessa A + válida, mas o objeto em si não é uma promessa.

Isso foi feito especificamente para permitir que você use a sintaxe:

  return knex.schema.createTable('first', function(table) {
    table.increments('id');
    table.string('name');
  })
  .createTable('second', function(table) {
      table.increments('id');
      table.string('name');
  }).then(function() {
    // all done
  });

que deve executar as migrações conforme o esperado na mesma conexão em sequência.

Além disso, não há necessidade de Promise.all aqui, pois isso teria o mesmo efeito:

exports.up = function(knex, Promise) {
  return knex.schema.createTable('first', function(table) {
    table.increments('id');
    table.string('name');
  }).then(function() {
    return knex.schema.createTable('second', function(table) {
      table.increments('id');
      table.string('name');
    });
  });
};

embora, se você quisesse, você poderia fazer:

exports.up = function(knex, Promise) {
  var first = knex.schema.createTable('first', function(table) {
    table.increments('id');
    table.string('name');
  }).then(); // coerce thenable to a promise

  var second = first.then(function() {
    return knex.schema.createTable('second', function(table) {
      table.increments('id');
      table.string('name');
    });
  });

  return Promise.all([first, second]);
};

e as coisas funcionariam conforme definido pela especificação.

Todos 10 comentários

Não acho que você deva retornar Promise.all([first, second]); , uma vez que você já acorrentou second a first . Você poderia tentar apenas return first ? Como seu segundo createTable está dentro de um then que está encadeado em first , não há necessidade de adicioná-lo a Promise.all , pois ele será executado de qualquer maneira, enquanto first está sendo resolvido.

A especificação de promessa diz que uma vez que uma promessa seja cumprida, ela nunca deve ser cumprida novamente e todas as invocações futuras de then devem retornar o resultado armazenado em cache. Se first e second forem ambos cumpridos, Promise.all([first, second]) deve ser ~ um ambiente autônomo.

return first tem o mesmo comportamento - ou seja, tenta criar as tabelas first , first e second , que é _ainda mais errado_, como deveria nunca haja nenhum código que realmente avalie qualquer coisa dentro da função passada para first.then nesse cenário.

Uhm ok, isso soa estranho. Não usar Promise.all pode dar um pequeno impulso no desempenho;)

Mas sim, isso parece estar errado. Vou marcar isso como um bug por agora e esperar até que @tgriesser entre em

Haha, eu particularmente não me importo com o valor de um tick de desempenho em minhas migrações: p

Desculpe, fiz uma afirmação errada acima - return first ainda deve criar a segunda tabela, só que o resultado da chamada de .then no resultado da função não vai esperar por isso. O que, c / e - definitivamente ainda não deve fazer first , first , second !

Não tenho certeza se entendi bem: Por que a 2ª mesa não esperaria pela criação da primeira? Uma vez que você interrompeu a primeira promessa usando then eles serão efetivamente executados em série. Isso não deve ser um problema.

A criação da segunda tabela irá, sim, mas o resultado da função exports.up não. Ou seja, se você chamar exports.up().then , é o mesmo que chamar first.then e não second.then - porque _is_ first.then !

Ah, não entendi você - sim, você está certo sobre isso!

Portanto, a confusão aqui é que o método createTable não retorna uma promessa, mas sim um objeto SchemaBuilder , que é um "thenable", ou seja, chamando .then no objeto retornará uma promessa A + válida, mas o objeto em si não é uma promessa.

Isso foi feito especificamente para permitir que você use a sintaxe:

  return knex.schema.createTable('first', function(table) {
    table.increments('id');
    table.string('name');
  })
  .createTable('second', function(table) {
      table.increments('id');
      table.string('name');
  }).then(function() {
    // all done
  });

que deve executar as migrações conforme o esperado na mesma conexão em sequência.

Além disso, não há necessidade de Promise.all aqui, pois isso teria o mesmo efeito:

exports.up = function(knex, Promise) {
  return knex.schema.createTable('first', function(table) {
    table.increments('id');
    table.string('name');
  }).then(function() {
    return knex.schema.createTable('second', function(table) {
      table.increments('id');
      table.string('name');
    });
  });
};

embora, se você quisesse, você poderia fazer:

exports.up = function(knex, Promise) {
  var first = knex.schema.createTable('first', function(table) {
    table.increments('id');
    table.string('name');
  }).then(); // coerce thenable to a promise

  var second = first.then(function() {
    return knex.schema.createTable('second', function(table) {
      table.increments('id');
      table.string('name');
    });
  });

  return Promise.all([first, second]);
};

e as coisas funcionariam conforme definido pela especificação.

Ok, isso explica isso. Não sabia disso - desculpe a confusão por não ser capaz de dar uma resposta direta de imediato @SohumB

Sim, temos gráficos de dependência mais complicados do que isso; este foi apenas um caso de teste mínimo! Obrigado pela ajuda.

@johanneslumpe

sohum<strong i="8">@diurnal</strong> ~ % cat test.js
var Promise = require('bluebird');

function promises() {
  var first = Promise.resolve('wee');
  var second = first.then(function() {
    console.log('delayin');
    return Promise.delay(1000);
  }).then(function() {
    console.log('done!');
  });
  return first;
}

promises().then(function() { console.log('yep, first is finished'); });

sohum<strong i="9">@diurnal</strong> ~ % node test.js
delayin
yep, first is finished
done!
Esta página foi útil?
0 / 5 - 0 avaliações

Questões relacionadas

aj0strow picture aj0strow  ·  3Comentários

nklhrstv picture nklhrstv  ·  3Comentários

arconus picture arconus  ·  3Comentários

mtom55 picture mtom55  ·  3Comentários

tjwebb picture tjwebb  ·  3Comentários