【问题标题】:Fulfill (don't resolve) promise with another promise用另一个承诺履行(不解决)承诺
【发布时间】:2015-11-17 01:03:17
【问题描述】:

我想用其他的诺言来兑现一个诺言。关键是我真的想在第一个承诺完成后立即访问(仍在等待中的)第二个 promise。不幸的是,我似乎只能在两个 Promise 都实现后才能获得第二个 Promise 的分辨率值。

这是我想到的用例:

var picker = pickFile();

picker.then(  // Wait for the user to pick a file.
    function(downloadProgress) {
        // The user picked a file. The file may not be available just yet (e.g.,
        // if it has to be downloaded over the network) but we can already ask
        // the user some more questions while the file is being obtained in the
        // background.

        ...do some more user interaction...

        return downloadProgress;
    }
).then( // Wait for the download (if any) to complete.
    function(file) {
        // Do something with the file.
    }
)

pickFile 函数显示一个文件选择器,用户可以在其中从自己的硬盘驱动器或 URL 中选择文件。它返回一个承诺picker,一旦用户选择了一个文件,该承诺就会实现。此时,我们可能仍然需要通过网络下载选定的文件。因此,我无法使用所选文件作为分辨率值来满足picker。相反,picker 应该通过另一个承诺 downloadProgress 来实现,而这最终将通过所选文件来实现。

为了完整起见,这里是 pickFile 函数的模拟实现:

function pickFile() {
    ...display the file picker...

    var resolveP1 = null;

    var p1 = new Promise(
        function(resolve, reject) {
            resolveP1 = resolve;
        }
    );

    // Mock code to pretend the user picked a file
    window.setTimeout(function() {
        var p2 = Promise.resolve('thefile');
        resolveP1(p2);  // <--- PROBLEM: I actually want to *fulfill* p1 with p2
    }, 3000);

    return p1;
}

标记行中的问题是我想履行承诺p1与新承诺p2,但我只知道如何解决它。 difference between fulfilling and resolving 是解析首先检查提供的值 p2 是否再次是一个承诺。如果是,则p1 的实现将推迟到p2 实现,然后p1 将使用p2 的分辨率值而不是p2 本身来实现。

我可以通过围绕p2 构建一个包装器来解决这个问题,即替换该行

        resolveP1(p2);  // <--- PROBLEM: I actually want to *fulfill* p1 with p2

来自第二个代码示例

        resolveP1({promise: p2});

然后,在第一个代码示例中,我必须替换该行

        return downloadProgress;

通过

        return downloadProgress.promise;

但是当我真正想做的只是履行(而不是解决)一个承诺时,这似乎有点像一个黑客。

如果有任何建议,我将不胜感激。

【问题讨论】:

  • Promise 会自动解包,避免这种情况的唯一方法是不使用另一个 Promise 解决,正如您所提到的。最初使用文件名或其他数据解析 result 对象,然后在其上传时解析 .file 函数或属性是否有缺点?
  • 不幸的是,这在 ES6 Promise 中是不可能的。我担心这将永远无法解决,因为解决方法太简单了。
  • 您的示例是演示场景还是您的团队用例?如果那是您的用户案例,那么您的事情就过于复杂了。这里唯一的异步操作是下载,所以要保证文件,然后在同步代码中完成其余的操作。一旦所有同步代码完成后,就会有一个执行函数处理该文件。
  • @Amit:也许我称它为“文件选择器”是一种误导。这不是与&lt;input type="file"&gt; 关联的模态“打开文件”对话框。相反,pickFile 显示带有一些输入字段的&lt;form&gt; 以选择文件(本地或通过 URL)和“继续”按钮。因此,使pickFile 同步将冻结浏览器并且用户将无法与表单元素进行交互。或者,从 pickFile 返回一个仅在下载完成后才解决的承诺意味着用户必须等待下载,即使该文件不是立即需要的。
  • @loganfsmyth:我想,在这种特定情况下,将返回的承诺包装在一个对象中是可行的,因为我已经知道我想要传递的值是一个承诺。我只是认为我错过了一些明显的替代方案,因为我认为用 any 类型的值(无论是否是承诺)履行承诺是一件相当通用的事情,所以我没有预计这是不可能的。 (假设您正在编写一个想要从异步操作“返回”一些用户定义的值的库,其中该库无法对值的类型做出任何假设)。还是谢谢!

标签: javascript ecmascript-6 es6-promise


【解决方案1】:

除了我在问题中已经描述的解决方法之外,似乎没有解决方案。为了将来参考,如果你想实现(而不是解决)带有值 val 的承诺 p,其中 val 是另一个承诺,那么只需使用参数 val 调用 p 的承诺解析函数不会按预期工作。这将导致p 被“锁定”在val 的状态下,这样一旦val 被满足,p 就会被val 的分辨率值满足(参见spec)。

相反,将val 包装在另一个对象中并使用该对象解析p

var resolveP;  // Promise resolution function for p

var p = new Promise(
    function(resolve, reject) {
        resolveP = resolve;
    }
);

function fulfillPwithPromise(val) {  // Fulfills p with a promise val
    resolveP({promise: val});
}

p.then(function(res) {
    // Do something as soon as p is fulfilled...

    return res.promise;
}).then(function(res) {
    // Do something as soon as the second promise is fulfilled...
});

如果您已经知道val 是一个承诺,则此解决方案有效。如果您无法对val 的类型做出任何假设,那么您似乎不走运。要么您必须始终将承诺解析值包装在另一个对象中,要么您可以尝试检测 val 是否具有类型为“function”的字段 then 并有条件地包装它。

也就是说,在某些情况下,promise 解析的默认行为实际上可能会产生预期的效果。因此,如果您确定要履行而不是解决第一个承诺和第二个承诺,请仅使用上述解决方法。

【讨论】:

  • 在接受自己的答案之前等待更多答案可能会更好。
  • 这个问答值得更多的投票。这是原生 Promises 的一个巨大缺点。幸运的是,补丁很简单。
【解决方案2】:

虽然不同的人使用不同的术语,但在通用术语中,“履行”意味着将承诺置于“成功”状态(而不是“拒绝”)——该状态将触发 then 处理程序挂起它。

换句话说,你不能用承诺来“履行”承诺。你可以用一个值来实现它。 (顺便说一句,“解决”一词通常意味着满足或拒绝。)

您可以做的是从.then 处理程序返回一个promise,这将具有实质上用返回的promise 替换原始promise 的效果。

这是一个简单的例子:

asyncTask1 . then(asyncTask2) . then(processData)

其中asyncTask1 是一个promise,asyncTask2 是一个返回promise 的函数。所以当asyncTask1被实现(成功完成),然后asyncTask2运行,.then返回的promise被asyncTask2返回的promise“接管”,这样当它完成时,数据就可以已处理。

我可以通过调用Promise.resolve 并使用promise 作为参数来做类似的事情。这有点用词不当,因为我在技术意义上解决了这个承诺。相反,新创建的 Promise 被我传入的 Promise “居住”了。它也没有用,因为使用结果与使用我传入的 Promise 完全相同:

Promise.resolve(asyncTask2)

行为与

完全相同
asyncTask2

(假设 asyncTask2 已经是一个 Promise;否则 Promise.resolve 具有创建一个 Promise 的效果,该 Promise 会立即用传入的值实现。)

就像您可以将一个promise 传递给Promise.resolve 一样,您可以将一个promise 传递给作为promise 构造函数回调的参数提供给您的resolve 函数。如果您传递给resolve 的参数是非承诺,则承诺会立即以该值实现。但是,如果您传递给resolve 的参数是另一个promise,则该promise 将“接管”您正在构建的promise 的主体。换句话说,你正在构建的 Promise 开始与传递给 resolve 的 Promise 完全相同。

我所说的“行为准确”是指,如果您传递给resolve 的承诺已经实现,那么您正在构建的承诺会立即以相同的值实现。如果你传递给resolve 的promise 已经被拒绝了,那么你正在构建的promise 会立即以同样的原因被拒绝。如果您传递给resolve 的promise 尚未解决,那么当您传递给resolve 的promise 被解决时,您挂起正在构建的promise 的任何then 处理程序都将被调用。

正如Promise.resolve 可能会导致一个实际上没有被解析的promise 令人困惑,同样令人困惑的是,调用作为参数传递给promise 构造函数的resolve 函数可能不 如果您使用未解决的承诺调用它,则实际上会解决正在构造的承诺。相反,正如我已经说过几次的那样,它的效果是使正在构建的 Promise 与传递给 resolve 的 Promise 处于完全一致的状态。

因此,除非我错过了您的问题的重点,否则pickfile 可以写成

function pickFile() {
  return new Promise(function(resolve, reject) {
    ...display the file picker...

    // Mock code to pretend the user picked a file
    window.setTimeout(function() {
        resolve('thefile');
    });
}

我并没有真正清楚地理解你的问题,所以这可能不是你想要的。如果您愿意,请说明。

【讨论】:

  • 我认为 OP 确实很好地理解了解决和实现之间的区别。他确实想履行一个承诺,因为一个承诺只是一个价值。
  • 我意识到,通俗地说,“履行”和“解决”一个承诺有时被用作同义词。我在ES6 spec 的意义上使用了这些术语,这清楚地区分了两者。从这个意义上说,resolve 函数的命名是正确的,正如规范所述:“如果一个 promise 被解决,或者它已经被‘锁定’以匹配另一个 promise 的状态,那么它就会被解析。” (其中“已解决”的意思是“履行或拒绝”。)
  • 您的实现的问题是文件在用户选择后可能无法立即使用。如果仍然需要下载,那么我希望由pickFile 启动和监控下载,但我仍然希望在用户选择文件后立即履行pickFile 返回的承诺(下载应该在背景)。
【解决方案3】:

在从 Angular 的 $q 迁移到原生 Promise 功能的过程中找到了类似的解决方案。 Promise.all 可以是一个选项(在独立并行异步任务的情况下),通过传递适当的对象或用状态装饰的东西,在适当的时候将其传递给任何准备好的东西。在下面的 Promise.all 示例中,请注意它是如何在其中一个承诺中恢复的——我花了一段时间才意识到如何重定向链的结果。一切的结果只是最后一个承诺的回报。虽然这不能回答问题的标题,但使用 return Promise.reject(&lt;an-object-including-a-promise&gt;)(或解决)可以在此过程中提供一系列和/或一组异步任务共享访问和控制。在选择,下载然后使用文件的情况下,我将取出进度事件处理然后执行:pickFile.then(download,orFailGracefully)downloadProgressdownload onResolve 处理程序中处理(下载进度似乎不是一个异步任务)。下面是控制台中的相关实验。

var q = {
defer: function _defer(){
    var deferred = { };
    deferred.promise = new Promise(function(resolve, reject){
        deferred.resolve = resolve;
        deferred.reject = reject;
    });
    return deferred;
    }
};

var communityThatCares = q.defer();
communityThatCares.promise.then(function(someGood){
    console.log('someGood', someGood);
    return someGood;
}, function(someBad){
    console.warn('someBad', someBad);
    return someBad;
});

(new Promise(function(resolve, reject){ communityThatCares.about = 'communityThatCares'; setTimeout(resolve,1000, communityThatCares); }))
.then(
function(e){
    console.log(3,e); return e.resolve(e);
}, function(e){
    console.warn(3, e); return e.reject(e);
});

var todo = {
    find:'swan'
};

var greaterGood = [(
(new Promise(function(res,rej){ res(todo); })).then(function(e){ e.stuff = 'things'; return e; }),
(new Promise(function(res,reject){ 
    reject(todo);
})).then(function(e){ return e; }
,function(e){
    console.warn(1,e);
    e.recover = 'uh oh';
    return Promise.resolve(e);
}).then(function(e){ console.log(2,e); return e; }),
(new Promise(function(res,rej){ res(todo); })).then(function(e){ console.log(1,e); e.schedule = 'today'; return e; },function(e){ console.warn(1,e); return e; }).then(function(e){ console.log(2,e); return e; }))
];

var nkay = Promise.all( greaterGood )
.then(function(todo){
    console.log('all',todo[0]); return todo;
}, function(todo){
    console.warn('all',todo[0]); return todo;
});

【讨论】:

  • 我可以向avoid the deferred antipattern 提出建议吗,或者这是您解决方案的故意部分?
  • 有用的东西@Bergi。编辑之前:问题尚不清楚,但似乎是“如何用另一个承诺或对象解决(或拒绝)承诺”,这是我的回答试图解决的问题。示例代码似乎不是解决所述有点模糊问题的最佳方法,即将进度事件与异步请求/承诺混合对我来说没有意义。最好删除答案还是从 Promise.all 调整到 promise.then.then 链(或其他什么?)?谢谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-06-05
  • 2014-06-04
  • 2021-06-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多