【问题标题】:Convert a Promise Based Approach to Callbacks将基于 Promise 的方法转换为回调
【发布时间】:2020-10-19 01:31:19
【问题描述】:

我是 js 新手。我已经阅读了许多关于异步性的先前堆栈溢出帖子,但仍然不明白下面的问题。

我有以下代码将图像文件上传到 S3 存储桶。代码需要实现的关键是让父级中的 image1 变量将信息存储在子函数中 s3.upload 调用的数据变量中。

我使用以下承诺的代码可以正常工作。我的理解是,同样的事情可以只使用回调来完成,我一直试图用回调重写下面的代码,只是作为一个学习练习,但它没有奏效。我将如何更改此代码以使用回调和没有承诺来做同样的事情?

父函数:

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

孩子:

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

【问题讨论】:

  • 您实际上已经这样做了, s3.upload() 正在使用带有 (err, data) 参数的回调。在您的示例中,您将回调的处理转换为 Promise。对于纯回调处理,您需要做的就是弄清楚当 s3.upload() 调用完成时您想对 err & 数据做什么。

标签: javascript node.js asynchronous promise callback


【解决方案1】:

首先,你需要给你的 async 函数添加一个回调 arg,去掉 async 关键字

const uploadToAWSBucket = (fileObject, callback) => {

接下来,您需要以回调方式处理 s3 响应,将 Promise 替换为回调用法。

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

或者你甚至可以将其简化为

s3.upload(AWSUploadObject, callback)

您还需要将您的使用更新为回调方式

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

最终结果是

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

就是这样。我希望这个解释能帮助你理解如何使用回调。

【讨论】:

  • 如果我们正在做与经典承诺相反的事情,那么uploadToAWSBucket 应该保持不变。我们应该编写一个调用uploadToAWSBucket 的适配器,并以调用提供的回调的方式处理其成功/错误。原始的uploadToAWSBucket 不应被破坏,其内部运作也无需复制。事实上,我们不需要知道uploadToAWSBucket的内部运作。
  • @Roamer-1888,我还没有在问题中看到这样的观点。我在这里看到了一个关于如何使用回调方法重构原始函数的请求。将某些东西从回调转换为 Promise 到 async/await 是典型的学习任务。我以为作者想将uploadToAWSBucket 实现重写为回调。看看作者的反馈吧。
  • 是的,同意,很难预测 OP 的期望。
  • 谢谢@Pavlo Zhukov。这很有帮助。
【解决方案2】:

基于回调的函数的Promisifcation 是well understood and well documented

我从未见过关于“去承诺”的讨论,但这很简单。

从你的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
};

或者你可以写一个通用的去承诺器......

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

...然后

const uploadToAWSBucketNodeback = depromisify(uploadToAWSBucket);

任何一种方法都可以让你写:

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

注意事项

  • 我们只需要知道原来的 asyncFunction 是 thenable/catchable。
  • 原始 asyncFunction 对 depromisified 函数完全不透明。
  • 我们不需要了解原始 asyncFunction 的内部工作原理。因此,AWSUploadObject 的组合不需要复制......它仍然由uploadToAWSBucket() 执行。

【讨论】:

    【解决方案3】:

    如果我的理解是正确的,你想在catch 块之后使用image1

    在这种情况下,我想,您将使用image1 调用某个函数。可以如下实现,部分sn-ps取自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.  
    
    });
    

    如果你想用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);
    
          });
    
        }); 
    
      }); 
    
    });
    

    基本上,如果您使用回调,则不能有局部全局变量。我将尝试解释问题情况。

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

    在上面的sn-p中,someOtherFunction会在uploadToAWSBucketCallback被执行之前被调用。这意味着,image1 没有分配给_image1。现在,您知道当调用someOtherFunctionimage1 的值是多少。

    第二个 sn-p 展示了如何通过将后续调用嵌套在回调中来将一个异步函数的结果传递给另一个。这使许多人的代码可读性降低。有像 async 这样的库,这有助于使事情变得更容易和可读。

    第二个sn-p可以用async库的waterfall函数重写,如下所示:

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

    【讨论】:

    • 明确的答案,你期待我的核心后续行动(如何处理在另一个函数调用中需要第一个回调的结果的情况)。 @explorer
    【解决方案4】:

    在阅读了大家的回复后,我想出了以下我认为可行的方法,基本上是@Pavlo Zhukov 建议的。 (请注意,函数名称与我之前的帖子略有不同。)

    来自父函数的代码:

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

    子函数:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-04-23
      • 2018-02-08
      • 2017-03-22
      • 2015-12-11
      • 2015-12-02
      • 1970-01-01
      • 2016-08-11
      • 1970-01-01
      相关资源
      最近更新 更多