Knex: Las promesas no se comportan como se esperaba en las migraciones

Creado en 16 jun. 2014  ·  10Comentarios  ·  Fuente: knex/knex

(Esto puede ser un duplicado del # 312)

Un archivo de migración 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) {

};

da el siguiente resultado:

{ __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

Comentario más útil

Entonces, la confusión aquí es que el método createTable no devuelve una promesa, sino que devuelve un objeto SchemaBuilder , que es un "thenable", es decir, llamar a .then object devolverá una promesa A + válida, pero el objeto en sí mismo no es una promesa.

Esto se hizo específicamente para permitirle usar la sintaxis:

  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 debería ejecutar las migraciones como se esperaba en la misma conexión en secuencia.

Además, no hay necesidad de Promise.all aquí, ya que esto lograría lo mismo:

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');
    });
  });
};

aunque si quisieras, podrías hacer:

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]);
};

y las cosas funcionarían según lo definido por la especificación.

Todos 10 comentarios

No creo que debas devolver Promise.all([first, second]); , ya que ya has encadenado second a first . ¿Podrías intentar solo return first ? Dado que su segundo createTable está dentro de un then que está encadenado fuera de first , no es necesario agregarlo a Promise.all , ya que se ejecutará de todos modos mientras se resuelve first .

La especificación de la promesa dice que una vez que se cumple una promesa, nunca debe volver a cumplirse, y todas las invocaciones futuras de then deben devolver el resultado en caché. Si first y second se cumplen, entonces Promise.all([first, second]) debería ser ~ una operación no operativa.

return first tiene el mismo comportamiento, es decir, intenta crear las tablas first , first y second , lo cual es _aún más incorrecto_, como debería nunca habrá ningún código que realmente evalúe algo dentro de la función pasada a first.then en ese escenario.

Uhm, eso suena raro. Sin embargo, no usar Promise.all podría dar un pequeño impulso al rendimiento;)

Pero sí, esto parece estar mal. Lo marcaré como un error por ahora y esperaré hasta que @tgriesser intervenga . ¡Gracias por informar!

Jaja, no me importa particularmente el rendimiento de un tick en mis migraciones: p

Lo siento, hice una declaración incorrecta arriba: return first aún debería crear la segunda tabla, es solo que el resultado de llamar a .then en el resultado de la función no lo esperará. Lo cual, w / e - definitivamente no debería ser first , first , second !

No estoy seguro de haber entendido completamente: ¿Por qué la segunda mesa no esperaría la creación de la primera? Dado que encadena la primera promesa usando then , se ejecutarán efectivamente en serie. Eso no debería ser un problema.

La creación de la segunda tabla sí, pero el resultado de la función exports.up no lo hará. Es decir, si llama a exports.up().then , es lo mismo que llamar a first.then y no a second.then - ¡porque es _ first.then !

Ah, te entendí mal allí, ¡sí, tienes razón en eso!

Entonces, la confusión aquí es que el método createTable no devuelve una promesa, sino que devuelve un objeto SchemaBuilder , que es un "thenable", es decir, llamar a .then object devolverá una promesa A + válida, pero el objeto en sí mismo no es una promesa.

Esto se hizo específicamente para permitirle usar la sintaxis:

  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 debería ejecutar las migraciones como se esperaba en la misma conexión en secuencia.

Además, no hay necesidad de Promise.all aquí, ya que esto lograría lo mismo:

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');
    });
  });
};

aunque si quisieras, podrías hacer:

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]);
};

y las cosas funcionarían según lo definido por la especificación.

Ok eso explica eso. No lo sabía, perdón por la confusión por no poder dar una respuesta directa de inmediato @SohumB

Sí, tenemos gráficos de dependencia más complicados que ese; ¡Este fue solo un caso de prueba mínimo! Gracias por la ayuda.

@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!
¿Fue útil esta página
0 / 5 - 0 calificaciones

Temas relacionados

zettam picture zettam  ·  3Comentarios

lanceschi picture lanceschi  ·  3Comentarios

ghost picture ghost  ·  3Comentarios

marianomerlo picture marianomerlo  ·  3Comentarios

sandrocsimas picture sandrocsimas  ·  3Comentarios