我没有得到ES6"承诺"

时间:2016-04-28 00:21:48

标签: javascript node.js callback promise

我正在编写一个Node.js应用程序:

  1. 接受一个文件(来自HTTP" POST"),然后
  2. 将文件写入Box.com存储库。
  3. 此Node.js代码用于从HTTP" POST"中读取文件数据。消息完美无缺:

     // ORIGINAL (non-Promise; parse only)
     app.post('/upload', function(req,res) {
       console.log('/upload...');
    
       var form = new multiparty.Form ();
       form.parse(req, function(err, fields, files) {
         res.writeHead(200, {'content-type': 'text/plain'});
         res.write('received upload:\n\n');
         res.end(util.inspect({fields: fields, files: files}));
     });
    

    问题在于,读取文件数据只是我需要做的几件事的第一,所有这些都涉及异步回调。所以我尝试使用promises来序列化调用(根据需要优雅地处理错误):

    var Promise = require('bluebird');
      ...
      // IDEAL SOLUTION (using "promises")
      var data = {};
      try {
        parsePostMsg(req, data))
        .then(sendAck(res, data))
        .then(writeTempFile())
        .then(sendToBox())
        .then(deleteTempFile());
      } catch (e) {
        console.log("app.post(/upload) ERROR", e);
        deleteTempFile();
      }
    

    问题:

    第一个函数parsePostMsg()本身有一个回调函数。永远不会被调用:

    function parsePostMsg(req, data) {
      console.log("parsePostMsg...");
      return new Promise(function(resolve, reject) {
        var form = new multiparty.Form ();
        form.parse(req, function(err, fields, files) {
          data.fields = fields; // <-- This never gets called, so fields & files
          data.files = files;   //     never get initialized!
        });
      });
    }
    

    问:我如何正确1)创建承诺,然后2)调用parsePostMsg()以便正确地沿着链调用3)form.parse()

    问:我是否甚至创造了#34;承诺&#34;正确的,在正确的地方????

    问:resolve()reject()怎么样?他们在哪里适合?

    =============================================== =======================

    ADDENDUM:我尝试了很多东西,到目前为止没有任何工作。

    目标:让这些功能(及其相关的回调,如果适用)按此顺序运行:

    1. parsePostMsg(req,data)//等待&#34;解析表单数据&#34;回调完成
    2. sendAck(res,data)//同步
    3. writeTempFile(data)//构建HTTP消息,发送它,等待来自远程服务器的响应
    4. deleteTempFile(data)//完成所有工作后进行清理
    5. 这是一个失败的例子:

      /*
       * If the "timeout" values are the same (e.g. "10"), everything works fine.
       *
       * But if the timeout values are *different*, the order gets scrambled:
       *     Invoking parsePostMsg...
       *     ... OK ...
       *     Done: data= {}
       *     writeTemp@setting data.temp { temp: 'foo' }
       *     sendAck@acknowledging {} { temp: 'foo' }
       *     deleteTempFile@callback: data.temp was  foo
       *     parsePostMsg@setting data.{fields, files} {} { temp: undefined, fields: [], files: [] }
       */
      var Promise = require('bluebird');
      
      var req = {}, res = {}, data = {};
      var temp;
      
      function parsePostMsg(req, data) {
        console.log("parsePostMsg...");
        return new Promise(function(resolve, reject) {
          setTimeout(function() {
            data.fields = [];
            data.files = [];
            console.log("parsePostMsg@setting data.{fields, files}", req, data);
            resolve();
          }, 35);
        });
      }
      
      function sendAck(req, data) {
        console.log("sendAck...");
        return new Promise(function(resolve, reject) {
          setTimeout(function() {
            console.log("sendAck@acknowledging", req, data);
            resolve();
          }, 5);
        });
      }
      
      function writeTempFile(data) {
        console.log("writeTemp...");
        return new Promise(function(resolve, reject) {
          setTimeout(function() {
            data.temp = "foo";
            console.log("writeTemp@setting data.temp", data);
            resolve();
          }, 2);
        });
      }
      
      function deleteTempFile(data) {
        console.log("deleteTemp...");
        return new Promise(function(resolve, reject) {
          setTimeout(function() {
            console.log("deleteTempFile@callback: data.temp was ", data.temp);
            data.temp = undefined;
            resolve();
          }, 15);
        });
      }
      
      console.log("Invoking parsePostMsg...");
      parsePostMsg(req, data)
        .then(sendAck(res, data))
        .then(writeTempFile(data))
        .then(deleteTempFile(data));
      console.log("Done: data=", data);
      

4 个答案:

答案 0 :(得分:3)

您失败的示例不起作用,因为您没有正确链接。

让我们看一下这段代码:

parsePostMsg(req, data)
    .then(sendAck(res, data))
    .then(writeTempFile(data))
    .then(deleteTempFile(data))
    // This console.log will execute way before
    // the promise is resolved
    console.log("Done: data=", data);

发生的事情是正确调用parsePostMsg(),但之后的所有内容都将在promise解析之前执行。这是因为您实际上是在立即执行这些函数,然后这些执行的输出就是promise在解析时将尝试使用的内容。这就是为什么,如果你将parsePostMsg()中的超时设置为几秒钟,那么该函数的输出将被记录到最后。

所以这应该是这样的:

parsePostMsg(req, data)
    .then(sendAck)
    .then(writeTempFile)
    .then(deleteTempFile)
    .then(function () {
        // Now the console.log will log when everything is done.
        console.log("Done: data=", data);
    });

在这里,你告诉它承诺在promise解决时应该执行这些函数。但要做到这一点,我们必须以正确的方式建立承诺链。要将这些方法链接在一起,我们必须在前一个函数中返回我们想要在函数中使用的值。例如,我们必须在parsePostMsg()函数中返回sendAck()函数的参数。

让我们编写parsePostMsg,以便它返回链中所需的所有参数。

function parsePostMsg(req, res, data) {
    console.log("parsePostMsg...");
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            data.fields = [];
            data.files = [];
            console.log("parsePostMsg@setting data.{fields, files}", req, data);

            // Here we pass all the arguments into the resolve method
            // This means that the following then() call will receive
            // These argument. Take note that this is an array.
            resolve([req, res, data]);
        }, 3000);
    });
}

现在我们已经更改了parsePostMsg,以便它将在链中传递它的所有参数。让我们以同样的方式改变其他方法。

function sendAck(req, res, data) {
    console.log("sendAck...");
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            console.log("sendAck@acknowledging", res, data);
            resolve([req, res, data]);
        }, 3000);
    });
}

function writeTempFile(req, res, data) {
    console.log("writeTemp...");
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            data.temp = "foo";
            console.log("writeTemp@setting data.temp", data);
            resolve([req, res, data]);
        }, 3000);
    });
}

function deleteTempFile(req, res, data) {
    console.log("deleteTemp...");
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            console.log("deleteTempFile@callback: data.temp was ", data.temp);
            data.temp = undefined;
            resolve([req, res, data]);
        }, 3000);
    });
}

现在请注意,所有函数都接受3个参数,但我们使用数组调用resolve方法。如果我们只是简单地使用.then(),那么它将无法正常工作,但bluebird提供了一个名为.spread()的特殊辅助方法,它解析数组并使用数组的所有成员调用该方法。请注意,如果您使用的是ES2015,则可以使用ES2015销毁而不是使用.spread(

因此,使用.spread()代码应如下所示:

parsePostMsg(req, res, data)
    .spread(sendAck)
    .spread(writeTempFile)
    .spread(deleteTempFile)
    .spread(function (req, res, data) {
        // Now the console.log will log when everything is done.
        console.log("Done: data=", data);
    });

这是您提供正确工作的失败示例:

var Promise = require('bluebird');

var req = {},
    res = {},
    data = {};
var temp;

function parsePostMsg(req, res, data) {
    console.log("parsePostMsg...");
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            data.fields = [];
            data.files = [];
            console.log("parsePostMsg@setting data.{fields, files}", req, data);
            resolve([req, res, data]);
        }, 3000);
    });
}

function sendAck(req, res, data) {
    console.log("sendAck...");
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            console.log("sendAck@acknowledging", res, data);
            resolve([req, res, data]);
        }, 3000);
    });
}

function writeTempFile(req, res, data) {
    console.log("writeTemp...");
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            data.temp = "foo";
            console.log("writeTemp@setting data.temp", data);
            resolve([req, res, data]);
        }, 3000);
    });
}

function deleteTempFile(req, res, data) {
    console.log("deleteTemp...");
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            console.log("deleteTempFile@callback: data.temp was ", data.temp);
            data.temp = undefined;
            resolve([req, res, data]);
        }, 3000);
    });
}

console.log("Invoking parsePostMsg...");

parsePostMsg(req, res, data)
    .spread(sendAck)
    .spread(writeTempFile)
    .spread(deleteTempFile)
    .spread(function (req, res, data) {
        console.log("Done: data=", data);
    })
    .catch(function(err) {
        // Always put a .catch() at the end of your promise chains, ALWAYS,
    // it is literally the ultimate method to handle promise errors.
        console.warn(err);
    });

答案 1 :(得分:2)

在此之前,我想提一下Bluebird有一个名为promisify的函数,它将基于回调的函数转换为promise-returns函数。

但是在引擎盖下......

  

创建承诺

你已经走上正轨了。承诺是具有2个状态(已解决和拒绝)的对象。您需要做的就是在解析或拒绝时定义。这就是resolvereject函数的用途。

function parsePostMsg(req, data) {

  // Return a promise
  return new Promise(function(resolve, reject) {
    var form = new multiparty.Form();

    // that callse your async, callback-ish function during construction
    form.parse(req, function(err, fields, files) {

      // that rejects when there's an error
      if(err) reject(err);

      // or resolves when everything goes well
      else resolve({ fields: fields, files: files });

    });
  });
}
  

调用parsePostMsg()

要知道你的promisified异步操作何时解析或拒绝,promises会公开一个接受2个回调的then方法。第一个在他们承诺解决时执行,第二个在承诺被拒绝时执行。它们都是在调用resolve / reject时使用的参数。

在这种情况下,当promise被拒绝时,我们期望来自form.parse的错误,或者当promise被解析时,我们期望包含字段和文件的对象。

// parsePostMsg returns a promise where we hook a then
parsePostMsg(req, data).then(function(data){

  // The object you passed to `resolve` is `data` in here
  // data.fields
  // data.files

}, function(error){

  // The `err` you passed to `reject` is `error` in here

})
  

我是否正确地在正确的地方创造了“承诺”????

参见第一栏

  

resolve()和reject()怎么样?他们在哪里适合?

参见第一栏

答案 2 :(得分:2)

我对承诺不熟悉,但你应该读一下,https://promisesaplus.com/,我认为你的尝试捕获也没用,因为承诺抛出的任何异常都与该代码分开。

resolve函数是在履行承诺时运行的函数,拒绝是在失败时运行,拒绝是您可能希望进行错误处理的地方。请记住,附在承诺上的所有内容都与周围的代码无关。

答案 3 :(得分:0)

你真的非常接近!您正在正确创建承诺并使用.then调用它。不幸的是,你链条中的下一步永远不会开始,因为你的承诺永远不会解决!

resolvereject是承诺的说法&#34;这有效,继续下一步&#34;或者&#34;这没有用,抛出一个错误!&#34;分别。在你的情况下,你可能想要做这样的事情:

function parsePostMsg(req, data) {
  console.log("parsePostMsg...");
  return new Promise(function(resolve, reject) {
    var form = new multiparty.Form ();
    form.parse(req, function(err, fields, files) {
      if (err) {
        reject(err)
      } else {
        data.fields = fields;
        data.files = files;
        resolve();
      }
    });
  });
}

请注意,使用承诺模型,您可以&#34;返回&#34;在内部方法中使用外部方法中的resolve

请注意,您需要确保链中的所有步骤都遵循此模式。我使用setTimeout模拟异步函数调用,将一个简单的示例放在一起,以显示它的样子:

&#13;
&#13;
var data = {};
var req, res;
var temp;

function parsePostMsg(req, data) {
  console.log("parsePostMsg...");
  return new Promise(function(resolve, reject) {
    setTimeout(function() {
      data.fields = [];
      data.files = [];
      resolve();
    }, 10);
  });
}

function sendAck(req, data) {
  console.log("sendAck...");
  return new Promise(function(resolve, reject) {
    setTimeout(function() {
      console.log("acknowleding", req, data);
      resolve();
    }, 10);
  });
}

function writeTempFile() {
  console.log("writeTemp...");
  return new Promise(function(resolve, reject) {
    setTimeout(function() {
      temp = "foo";
      resolve();
    }, 10);
  });
}

function deleteTempFile() {
  console.log("deleteTemp...");
  return new Promise(function(resolve, reject) {
    setTimeout(function() {
      console.log("temp was ", temp);
      temp = undefined;
      resolve();
    }, 10);
  });
}

parsePostMsg(req, data)
  .then(sendAck(res, data))
  .then(writeTempFile())
  .then(deleteTempFile());
&#13;
&#13;
&#13;