Knex: Les promesses ne se comportent pas comme prévu dans les migrations

Créé le 16 juin 2014  ·  10Commentaires  ·  Source: knex/knex

(Cela peut être un double de # 312)

Un fichier de migration comme celui-ci:

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

};

donne la sortie suivante:

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

Commentaire le plus utile

Donc la confusion ici est que la méthode createTable ne retourne pas de promesse, mais renvoie plutôt un objet SchemaBuilder , qui est un "thenable", c'est-à-dire appeler .then sur le object retournera une promesse A + valide, mais l'objet lui-même n'est pas une promesse.

Cela a été fait spécifiquement pour vous permettre d'utiliser la syntaxe:

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

qui devrait exécuter les migrations comme prévu sur la même connexion dans l'ordre.

De plus, il n'y a pas besoin de Promise.all ici, car cela accomplirait la même chose:

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

si vous le vouliez, vous pourriez faire:

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

et les choses fonctionneraient comme défini par la spécification.

Tous les 10 commentaires

Je ne pense pas que vous devriez retourner Promise.all([first, second]); , puisque vous avez déjà enchaîné second à first . Pourriez-vous essayer de n'essayer que return first ? Puisque votre deuxième createTable est à l'intérieur d'un then qui est enchaîné de first , il n'est pas nécessaire de l'ajouter à Promise.all , car il sera exécuté de toute façon pendant que first est en cours de résolution.

La spécification de la promesse dit qu'une fois qu'une promesse est remplie, elle ne devrait plus jamais l'être, et toutes les futures invocations de then devraient renvoyer le résultat mis en cache. Si first et second sont tous deux remplis, alors Promise.all([first, second]) devrait être ~ un no-op.

return first a le même comportement - c'est-à-dire qu'il essaie de créer les tables first , first et second , ce qui est encore plus faux ne jamais être un code qui évalue réellement quoi que ce soit à l'intérieur de la fonction passée à first.then dans ce scénario.

Ok, ça semble bizarre. Ne pas utiliser Promise.all pourrait cependant donner un petit coup de pouce aux performances;)

Mais oui, cela semble être faux. Je vais marquer cela comme un bug pour le moment et attendre que @tgriesser intervienne. Merci d'avoir signalé!

Haha, je ne me soucie pas particulièrement de la performance d'un tick dans mes migrations: p

Désolé, j'ai fait une mauvaise déclaration ci-dessus - return first devrait toujours créer la deuxième table, c'est juste que le résultat de l'appel de .then sur le résultat de la fonction ne l'attendra pas. Ce qui, sans - cela ne devrait certainement pas faire first , first , second !

Je ne suis pas sûr de bien comprendre: pourquoi la 2ème table n'attendrait-elle pas la création de la première? Puisque vous enchaînez la première promesse en utilisant then elles seront effectivement exécutées en série. Cela ne devrait pas être un problème.

La création de la deuxième table le sera, oui, mais le résultat de la fonction exports.up ne le sera pas. C'est-à-dire que si vous appelez exports.up().then , c'est la même chose que d'appeler first.then et non second.then - car il _is_ first.then !

Ah, vous avez mal compris - ouais vous avez raison!

Donc la confusion ici est que la méthode createTable ne retourne pas de promesse, mais renvoie plutôt un objet SchemaBuilder , qui est un "thenable", c'est-à-dire appeler .then sur le object retournera une promesse A + valide, mais l'objet lui-même n'est pas une promesse.

Cela a été fait spécifiquement pour vous permettre d'utiliser la syntaxe:

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

qui devrait exécuter les migrations comme prévu sur la même connexion dans l'ordre.

De plus, il n'y a pas besoin de Promise.all ici, car cela accomplirait la même chose:

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

si vous le vouliez, vous pourriez faire:

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

et les choses fonctionneraient comme défini par la spécification.

Ok ça explique ça. Je ne savais pas que - désolé pour la confusion de ne pas pouvoir donner une réponse directe tout de suite @SohumB

Oui, nous avons des graphiques de dépendances plus compliqués que cela; ce n'était qu'un cas de test minimal! Merci pour l'aide.

@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!
Cette page vous a été utile?
0 / 5 - 0 notes

Questions connexes

lanceschi picture lanceschi  ·  3Commentaires

sandrocsimas picture sandrocsimas  ·  3Commentaires

marianomerlo picture marianomerlo  ·  3Commentaires

zettam picture zettam  ·  3Commentaires

npow picture npow  ·  3Commentaires