简体   繁体   English

将基于 Promise 的方法转换为回调

[英]Convert a Promise Based Approach to Callbacks

I'm new to js.我是 js 新手。 I've read many of the prior stack overflow posts on asynchronicity but still don't understand the question below.我已经阅读了许多关于异步性的先前堆栈溢出帖子,但仍然不明白下面的问题。

I have the following code to upload an image file to an S3 bucket.我有以下代码将图像文件上传到 S3 存储桶。 The key thing the code needs to achieve is to have the image1 variable in the parent store the information in the data variable from the s3.upload call in the child function.代码需要实现的关键是让父级中的 image1 变量将信息存储在来自子级 function 中 s3.upload 调用的数据变量中。

My code using promises below works fine.我使用以下承诺的代码可以正常工作。 My understanding is that the same thing can be done using callbacks only, and I've been trying to rewrite the code below with callbacks only as a learning exercise, but it has not worked.我的理解是,同样的事情可以只使用回调来完成,我一直试图用回调重写下面的代码,只是作为一个学习练习,但它没有奏效。 How would I change this code to do the same thing with callbacks and no promises?我将如何更改此代码以使用回调而不是承诺来做同样的事情?

Parent function:父 function:

try {
      image1 = await uploadToAWSBucket(file);
}
catch (error) {
      return next(error);
}

Child:孩子:

const uploadToAWSBucket = async (fileObject) => {
  let randomFileName = shortid.generate();
  const AWSUploadObject = {
    Bucket: BUCKET_NAME,
    Key: randomFileName,
    Body: fileObject.buffer,
    ContentType: fileObject.mimetype,
  };

  return new Promise((resolve, reject) => {
    s3.upload(AWSUploadObject, (err, data) => {
      if (err) {
        return reject(err);
      }
      return resolve(data);
    });
  });   
};

At first, you need to add a callback arg to you async function, removing async keyword首先,您需要为您的 async function 添加一个回调 arg,删除 async 关键字

const uploadToAWSBucket = (fileObject, callback) => {

Next, you need to handle s3 response in a callback manner replacing Promise with callback usage.接下来,您需要以回调方式处理 s3 响应,将 Promise 替换为回调使用。

s3.upload(AWSUploadObject, (err, data) => {
  if (err) {
    callback(err);
    return;
  }
  callback(null, data);
});

Or maybe you can even simplify it to或者你甚至可以将其简化为

s3.upload(AWSUploadObject, callback)

You also need to update your usage to a callback manner您还需要将您的使用更新为回调方式

uploadToAWSBucket(file, (error, image1) => {
  if (error) {
    next(error);
    return;
  }
  // your success code here
});

The final result is最终结果是

const uploadToAWSBucket = (fileObject, callback) => {
  let randomFileName = shortid.generate();
  const AWSUploadObject = {
    Bucket: BUCKET_NAME,
    Key: randomFileName,
    Body: fileObject.buffer,
    ContentType: fileObject.mimetype,
  };


  s3.upload(AWSUploadObject, callback);
};

That's it.而已。 I hope this explanation will help you to understand how to use callbacks.我希望这个解释能帮助你理解如何使用回调。

If my understanding is correct, you want to use image1 after the catch block.如果我的理解是正确的,你想在catch块之后使用image1

In that case, I suppose, you will be calling some function with image1 .在这种情况下,我想你会用image1调用一些 function 。 It can be done as follows, with some snippets taken from this answer :可以按如下方式完成,其中一些片段来自此答案


const uploadToAWSBucket = (fileObject, callback) => { ... }; // described in the linked answer

uploadToAWSBucket(file, function callback(error, image1) {
  
  if(error) { return next(error); }

  someOtherFunction(image1, next); // "next" is passed as callback, with the assumption that nothing else needed to be called after that.  

});

If you want to call 2 more functions with the result of someOtherFunction , it can be done as follows:如果你想用someOtherFunction的结果再调用 2 个函数,可以按如下方式完成:

uploadToAWSBucket(file, function callback(error, image1) {
  
  if(error) { return next(error); }

  someOtherFunction(image1, function someOtherFunctionCb(error, someOtherFunctionResult) {

    if(error) { return next(error); }

    someOtherFunction2(someOtherFunctionResult, function someOtherFunction2Cb(error, someOtherFunction2Result) {

      if(error) { return next(error); }

      someOtherFunction3(someOtherFunction2Result, function someOtherFunction3Cb(error, someOtherFunction3Result) {

        if(error) { return next(error); }

        next(null, someOtherFunction3Result);

      });

    }); 

  }); 

});

Basically, you cannot have local global variables if you use callbacks.基本上,如果您使用回调,则不能拥有局部全局变量。 I will try to explain a problem situation.我将尝试解释一个问题的情况。


let image1 = null;

uploadToAWSBucket(file, function uploadToAWSBucketCallback(error, _image1) {
  
  if(error) { return next(error); }

  image1 = _image1;

});

someOtherFunction(image1, function someOtherFunctionCb(error, someOtherFunctionResult) {

  if(error) { return next(error); }

    ...

}); 

In the above snippet, someOtherFunction will be called before uploadToAWSBucketCallback is executed.在上面的代码片段中,将在执行uploadToAWSBucketCallback之前调用someOtherFunction That means, image1 is not assigned with _image1 .这意味着, image1没有分配_image1 Now, you know what will be the value of image1 when someOtherFunction is called.现在,您知道调用someOtherFunctionimage1的值是多少。

The second snippet shows how to pass result of one async function to another, by nesting the subsequent calls inside the callbacks.第二个片段显示了如何通过将后续调用嵌套在回调中,将一个异步 function 的结果传递给另一个。 This makes code less readable for many.这使许多人的代码可读性降低。 There are libraries like async , which helps to make things easier & readable.有像async这样的库,这有助于使事情变得更容易和可读。

The second snippet can be rewritten with async library's waterfall function like this:第二个片段可以用async库的waterfall function 重写,如下所示:

async.waterfall([
    function uploadToAWSBucketStep(callback) {
        uploadToAWSBucket(file, callback);
    },
    function someOtherFunctionStep(image1, callback) {
        someOtherFunction(image1, callback);
    },
    function someOtherFunction2Step(someOtherFunctionResult, callback) {
        someOtherFunction2(someOtherFunctionResult, callback);
    },
    function someOtherFunction3Step(someOtherFunction2Result, callback) {
        someOtherFunction3(someOtherFunction2Result, callback);
    }
], function lastStep(error, someOtherFunction3Result) {
    if(error) { return next(error); };
    next(null, someOtherFunction3Result);
});

Promisifcation of a callback-based function is well understood and well documented .基于回调的 function 的承诺是很好理解和有据可查的。

I have never seen a discussion of "de-promisification", but it is pretty simple.我从未见过关于“去承诺”的讨论,但这很简单。

Starting with your uploadToAWSBucket() and assuming you want your callback to be "nodeback" style (signature (err, data) ), then you can write:从您的uploadToAWSBucket()开始并假设您希望您的回调为“nodeback”样式(签名(err, data) ),那么您可以编写:

const uploadToAWSBucketNodeback = (fileObject, nodeback) => {
    uploadToAWSBucket(fileObject) // call the promise-returning "async" version.
    .then(data => { // success path
        nodeback(null, data);
    })
    .catch(nodeback); // error path
};

Or you could write a generic de-promisifier...或者你可以写一个通用的去承诺...

const depromisify = (asyncFunction) => {
    return function(...params) {
        let nodeback = params.pop(); // strip nodeback off the end of params
        asyncFunction(...params)
        .then(data => { // success path
            nodeback(null, data);
        })
        .catch(nodeback); // error path
    }
};

... then ... 然后

const uploadToAWSBucketNodeback = depromisify(uploadToAWSBucket);

Either approach will allow you to write:任何一种方法都允许您编写:

uploadToAWSBucketNodeback(fileObject, function(err, data)) {
    if(err) {
        // handle error
    } else {
        // handle data
    }
}

Notes笔记

  • we just need to know that the original asyncFunction is thenable/catchable.我们只需要知道原始的 asyncFunction 是 thenable/catchable 即可。
  • the original asyncFunction is completely opaque to the depromisified function.原始的 asyncFunction 对取消承诺的 function 完全不透明。
  • we don't need to know anything about the internal workings of the original asyncFunction.我们不需要了解原始 asyncFunction 的内部工作原理。 Thus, the composition of AWSUploadObject doesn't need to be replicated..... it is still performed by uploadToAWSBucket() .因此,不需要复制AWSUploadObject的组合......它仍然由uploadToAWSBucket()执行。

After reading everyone's responses, I came up with the following which I think works and is basically what @Pavlo Zhukov suggested.在阅读了每个人的回复后,我想出了以下我认为可行的方法,基本上是@Pavlo Zhukov 建议的。 (Note the function names have changed slightly from my earlier post.) (注意 function 的名称与我之前的帖子略有不同。)

Code from parent function:来自父 function 的代码:

let image1;
uploadToAWSBucketCallbackStyle(file, (err, data) => {
  if (err) {
    return next(err);
  }
  image1 = data;
  // Do stuff with image1 here or make additional function
  // calls using image1.
});


Child function:儿童 function:

const uploadToAWSBucketCallbackStyle = (fileObject, callback) => {
  let randomFileName = shortid.generate();
  const AWSUploadObject = {
    Bucket: BUCKET_NAME,
    Key: randomFileName,
    Body: fileObject.buffer,
    ContentType: fileObject.mimetype,
  };

  s3.upload(AWSUploadObject, callback);
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM