2016-04-28 59 views
3

我正在写的Node.js应用程序是:我只是没有得到ES6“承诺”

  1. 接受文件(从HTTP“POST”),然后
  2. 将文件写入一个Box.com存储库。

这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()正确地被沿链调用?

问:我甚至在正确的位置创建了“承诺”吗?

问:那么resolve()reject()呢?它们在哪里适合?

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

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

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

  1. parsePostMsg(REQ数据)//等待“解析表单数据”回调来完成
  2. sendAck(RES,数据)//同步
  3. writeTempFile(数据)//建立的HTTP消息,发送,并等待来自远程服务器
  4. deleteTempFile(数据)//清除响应当一切都做

这里是一个失败的例子:

/* 
* 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= {} 
*  [email protected] data.temp { temp: 'foo' } 
*  [email protected] {} { temp: 'foo' } 
*  [email protected]: data.temp was foo 
*  [email protected] 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("[email protected] data.{fields, files}", req, data); 
     resolve(); 
    }, 35); 
    }); 
} 

function sendAck(req, data) { 
    console.log("sendAck..."); 
    return new Promise(function(resolve, reject) { 
    setTimeout(function() { 
     console.log("[email protected]", req, data); 
     resolve(); 
    }, 5); 
    }); 
} 

function writeTempFile(data) { 
    console.log("writeTemp..."); 
    return new Promise(function(resolve, reject) { 
    setTimeout(function() { 
     data.temp = "foo"; 
     console.log("[email protected] data.temp", data); 
     resolve(); 
    }, 2); 
    }); 
} 

function deleteTempFile(data) { 
    console.log("deleteTemp..."); 
    return new Promise(function(resolve, reject) { 
    setTimeout(function() { 
     console.log("[email protected]: 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); 
+0

入住文档.then'是什么'应该接受https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/然后 – zerkms

+3

要使用promise,所有异步操作都需要“promisified”并返回一个promise在异步操作成功完成或拒绝时使用该值进行解析操作失败的原因。 – jfriend00

+0

您需要从[如何将现有的回调API转换为承诺?](http://stackoverflow.com/q/22519784/1048572) – Bergi

回答

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()正确调用,但是一切的承诺得到解决之前,之后将执行方式。这是因为你实际上是在立即执行这些函数,然后这些执行的输出就是这个承诺在解析时会尝试使用的内容。这就是为什么,如果您将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); 
    }); 

在这里,你是在告诉时承诺解决它应该执行功能的承诺。但要做到这一点,我们必须以正确的方式建立承诺链。要将这些方法链接在一起,我们必须在前面的函数中返回我们想在函数中使用的值。例如,我们必须在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("[email protected] 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("[email protected]", 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("[email protected] 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("[email protected]: data.temp was ", data.temp); 
      data.temp = undefined; 
      resolve([req, res, data]); 
     }, 3000); 
    }); 
} 

现在注意到所有函数都带有3个参数,但我们正在用数组调用resolve方法。如果我们简单地使用.then(),它将不会像现在这样工作,但蓝鸟提供了一个称为.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("[email protected] 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("[email protected]", 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("[email protected] 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("[email protected]: 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

BRILLIANT - 谢谢!在我失踪的(关键点!)中:1)使用“spread()”与“。然后()“,2)使用”resolve“将结果从一个”thenable“函数传递给下一个(不需要全局变量),3)通过仅传递函数名来调用函数的重要性 - * WITHOUT *”() “*和*没有*参数列表,它的工作原理就像一个魅力,谢谢 – paulsm4

+1

欢迎:) – grimurd

+1

只是你知道,使用新的Promise()并不是必须的,除了我的经验中的特殊情况。我建议使用Promise.try()来启动承诺链,或者如果您使用的是使用回调的模块,请按照@JosephTheDreamer的建议使用蓝鸟Promisify。您可以在大多数情况下简单地在承诺中返回一个值功能和那将沿着链传递 – grimurd

0

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

resolve and reject是Promise的一种说法,“这工作,继续下一步”或“这没用,抛出一个错误!”分别。在你的情况,你可能想要做这样的事情:

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

注意如何,与承诺的模式,你可以在“回归”使用从外部方法resolve从内部方法中!

请注意,您需要确保链中的所有步骤都遵循此模式。我扔在一起使用setTimeout嘲笑异步函数调用向你展示排序它会是什么样一个简单的例子:

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

+0

感谢您提供非常出色的回复...但它仍然不工作。如果我按原样运行您的示例,它是完美的。但是,如果我将“超时”值修改为随机值......回调顺序变得紊乱。具有较短超时的回调将首先运行;更长的超时时间最后。我试图强制“sendAck”等到parsePostMsg信号“完成”,使“writeTempFile”等待“sendAck”完成等。问:如何执行“订单”?我如何“序列化”这些电话? – paulsm4

2

别的之前,我想提一提, Bluebird有一个叫做promisify的函数,它将基于回调的函数转换为promise函数。

但引擎盖下...

创建承诺

你已经在正确的轨道上。承诺是一个有两个状态的对象(已解决并被拒绝)。所有你需要做的是定义它解决或拒绝。这就是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异步操作可解决或拒绝,承诺暴露then方法,该方法接受2个回调。第一个是在他们答应解决时执行的,第二个在promise被拒绝时执行。他们都通过调用resolve/reject时使用的参数。

在这种情况下,我们预计承诺拒绝时的form.parse错误或承诺解决时包含字段和文件的对象出错。

// 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 

}) 

上午我甚至创造了“无极”正确,在正确的地方????

请参阅第一块

什么决心()和拒绝()?它们在哪里适合?

请参阅第一块

2

我是半新的承诺,但你应该阅读这一点,https://promisesaplus.com/,你也尝试捕捉是没用的,我认为,由于受许抛出的异常是从独立码。

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