如何在JavaScript中实现异步函数之间的依赖?
How to implement dependency between asynchronous functions in JavaScript?
作为一个简化的案例,我有两个异步函数,foo
和 bar
。 bar
需要foo
的结果,即bar
依赖于foo
。我不知道哪个函数会先被调用。
- 如果先调用
bar
,bar
将调用 foo
并在 foo
完成后立即开始。
- 如果
foo
先调用完成,bar
可以使用foo
的结果。
- 如果
foo
先调用,bar
在foo
完成之前调用,bar
需要等待foo
的结果。 (不要调用对 foo
的新调用,只需等待对 foo
的已触发调用)
我怎样才能做到这一点?
是否可以注册异步函数依赖链(类似于 require.js define['foo'], function() { bar(); }
中的依赖)?
我可以用$.deferred()
来实现吗?
如何?
你可以简单地认为这两个功能是独立的。这样,您就不会菊花链式地连接异步操作的依赖项。然后你可以有另一个模块使用它们。
由于他们执行异步操作,请考虑使用 promises。您可以使用 jQuery 的 deferreds 来实现兼容性。将延迟视为 read/write 而承诺是只读的。
// foo.js
define(function(){
return function(){
return new Promise(function(resolve, reject){
// Do async stuff. Call resolve/reject accordingly
});
};
});
// bar.js
define(function(){
return function(){
return new Promise(function(resolve, reject){
// Do async stuff. Call resolve/reject accordingly
});
};
});
// Your code (Excuse the CommonJS format. Personal preference)
define(function(require){
// Require both functions
var foo = require('foo');
var bar = require('bar');
// Use them
foo(...).then(function(response){
return bar();
}).then(function(){
// all done
});;
});
在这种情况下,标准方法是缓存较低级别的承诺。
通常你会在一些合适的外部范围内建立一个 js 普通对象作为 promise 缓存,并且在调用你的异步进程之前总是先在那里查找。
var promiseCache = {};
function foo() {
if(!promiseCache.foo) {
promiseCache.foo = doSomethingAsync();
}
return promiseCache.foo;
}
function bar() {
return foo().then(doSomethingElseAsync);
}
当然,如果合适的话,没有什么可以阻止您缓存更高级别的承诺。
function bar() {
if(!promiseCache.bar) {
promiseCache.bar = foo().then(doSomethingElseAsync);
}
return promiseCache.bar;
}
编辑:forceRefresh
特征
您可以通过传递(额外)参数强制函数刷新其缓存的承诺。
function foo(any, number, of, other, arguments, forceRefresh) {
if(forceRefresh || !promiseCache.foo) {
promiseCache.foo = doSomethingAsync();
}
return promiseCache.foo;
}
通过将 forceRefresh
作为最后一个参数,将其省略与传递 false
相同,并且 foo
将使用缓存的承诺(如果可用)。或者,传递 true
以保证调用 doSomethingAsync()
并刷新缓存值。
编辑 2:setName()/getName()
在 getName()
中使用 forceRefresh
机制:
setName(newName).then(getName.bind(null, true)); //set new name then read it back using forceRefresh.
或者,省略 forceRefresh
机制,假设缓存 属性 为 promiseCache.name
:
setName(newName).then(function() {
promiseCache.name = $.when(newName);//update the cache with a simulated `getName()` promise.
});
第一种方法更优雅,第二种方法更高效。
尝试使用可能的值 undefined
、 "pending"
、 true
创建一个对象 属性 ;当 obj.active
为 true
时调用 deferred.resolve()
,当 obj.active
为 "pending"
时调用 deferred.reject()
var res = {
active: void 0
};
var foo = function foo(state) {
var t;
var deferred = function(type) {
return $.Deferred(function(dfd) {
if (res.active === "pending" || state && state === "pending") {
res.active = "pending";
dfd.rejectWith(res, [res.active])
} else {
res.active = state || "pending";
t = setInterval(function() {
console.log(res.active)
}, 100);
setTimeout(function() {
clearInterval(t)
res.active = true;
dfd.resolveWith(res, [res.active])
}, 3000);
}
return dfd.promise()
})
.then(function(state) {
console.log("foo value", state);
return state
}, function(err) {
console.log("foo status", err)
return err
})
}
return deferred()
}
var bar = function bar(result) {
var deferred = function(type) {
return $.Deferred(function(dfd) {
if (result && result === true) {
setTimeout(function() {
dfd.resolveWith(result, [true])
}, 1500)
} else {
dfd.rejectWith(res, [res.active || "pending"])
};
return dfd.promise()
})
}
return deferred().then(function(data) {
console.log("bar value", data);
}, function(err) {
console.log("bar status", err);
})
}
$("button").click(function() {
$(this).is(":first")
? foo().then(bar, bar)
: bar(res.active === true ? res.active : "pending")
.then(foo, foo).then(bar, bar)
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js">
</script>
<button>foo</button>
<button>bar</button>
不确定我是否正确理解了问题。但这是我的看法:
将函数 foo 放入变量中
var foo_fn = function foo(foo_args){// Your async code goes here}
foo 是异步的,returns 在某些时候是异步的。在您对 foo 的定义中,我建议您使用 promises,该概念旨在以干净且可扩展的方式管理异步函数的组合。 jQuery 这个概念的实现在很多简单的用例中都很方便,但也有一些缺点,这让你在某些时候对使用遵循 Promises/A 规范的众多 promises 库之一很感兴趣。更多信息,您可以参考:
比照。 https://thewayofcode.wordpress.com/2013/01/22/javascript-promises-and-why-jquery-implementation-is-broken/ and https://blog.domenic.me/youre-missing-the-point-of-promises
所以,假设 foo 接受 args,returns 是一个承诺,后来解析为一些值。
var foo_fn = function foo(foo_args) {
return foo_fn.promise = new RSVP.Promise(解决,拒绝){<br>
// 你的异步代码在这里
}
}
这里我使用 RSVP promise 库,但任何遵循 Promises/A 规范的 promise 库都可以完成这项工作。
当bar被调用时,你可以这样做:
function bar (bar_args) {
var foo_promise = foo_fn.promise;
// if foo was called, whether the computation is in progress or finished,
// the foo_fn.promise field will be non-empty, as foo returns immediately
// with a promise anytime it is called
``
if (!foo.promise) {
// foo has not yet been called so call it
foo_promise = foo(foo_args);
}
foo_promise.then (function (foo_result) {/*some async code here*/})
}
注意:该解决方案与 Roamer-1888
提出的解决方案非常相似。一个区别是,在 Roamer 提案中,foo
函数在执行一次异步计算后将始终 return 相同的值。不知道这是否是预期的行为。在我的实现中,foo 执行异步。每次调用时计算。 bar
将使用存储在字段 foo_fn.promise
中的最新计算值。较早的计算已丢失,可能正在进行的计算未被考虑在内。
如果您打算在您的代码中经常使用此模式,您还可以创建一个在 define
模型上工作的函数
在 require.js
中发挥作用。
您将需要:
用于保存依赖项函数的注册表(在您的示例中为foo
)
依赖函数(在您的示例中为 bar
)将需要接受依赖函数计算值作为其签名的一部分。例如,依赖项的散列可以作为第一个参数传递,因此 bar 签名可以是:{foo: foo_result}, other_bar_args...
依赖函数必须遵循我之前回答的模型,即在执行时将它们的承诺值注册为属性。
提醒:您需要命名那些依赖函数以在其主体内引用它们,然后将该对象添加到注册表。
在 define
函数体中,将依赖函数包装到另一个函数中:
从注册表获取所有依赖项
获取所有依赖项值,必要时执行依赖项(类似于我之前的回答)。这意味着您最终会得到一个承诺列表,然后将其结果聚集在一起(RSVP.hash
例如使用 RSVP 承诺库)。我相信 jQuery 与 jQuery.when
具有相似的功能
您调用依赖函数 (bar
) 并将此结果哈希作为第一个参数,其他参数与包装函数相同
包装函数是新的bar
,所以当调用bar
时,将调用包装函数。
有点长,但应该可以。如果您想查看一些代码,请告诉我这是否是您要找的。无论如何,如果你要有复杂的异步。在您的代码中,使用兼容的 promise 库对您来说可能会很有趣。 $.deferred 也只能在你看不到更好的东西时使用,因为它会让你更难跟踪你的函数的行为:你需要跟踪这个 deferred 似乎能够推理你的所有地方程序。
作为一个简化的案例,我有两个异步函数,foo
和 bar
。 bar
需要foo
的结果,即bar
依赖于foo
。我不知道哪个函数会先被调用。
- 如果先调用
bar
,bar
将调用foo
并在foo
完成后立即开始。 - 如果
foo
先调用完成,bar
可以使用foo
的结果。 - 如果
foo
先调用,bar
在foo
完成之前调用,bar
需要等待foo
的结果。 (不要调用对foo
的新调用,只需等待对foo
的已触发调用)
我怎样才能做到这一点?
是否可以注册异步函数依赖链(类似于 require.js define['foo'], function() { bar(); }
中的依赖)?
我可以用$.deferred()
来实现吗?
如何?
你可以简单地认为这两个功能是独立的。这样,您就不会菊花链式地连接异步操作的依赖项。然后你可以有另一个模块使用它们。
由于他们执行异步操作,请考虑使用 promises。您可以使用 jQuery 的 deferreds 来实现兼容性。将延迟视为 read/write 而承诺是只读的。
// foo.js
define(function(){
return function(){
return new Promise(function(resolve, reject){
// Do async stuff. Call resolve/reject accordingly
});
};
});
// bar.js
define(function(){
return function(){
return new Promise(function(resolve, reject){
// Do async stuff. Call resolve/reject accordingly
});
};
});
// Your code (Excuse the CommonJS format. Personal preference)
define(function(require){
// Require both functions
var foo = require('foo');
var bar = require('bar');
// Use them
foo(...).then(function(response){
return bar();
}).then(function(){
// all done
});;
});
在这种情况下,标准方法是缓存较低级别的承诺。
通常你会在一些合适的外部范围内建立一个 js 普通对象作为 promise 缓存,并且在调用你的异步进程之前总是先在那里查找。
var promiseCache = {};
function foo() {
if(!promiseCache.foo) {
promiseCache.foo = doSomethingAsync();
}
return promiseCache.foo;
}
function bar() {
return foo().then(doSomethingElseAsync);
}
当然,如果合适的话,没有什么可以阻止您缓存更高级别的承诺。
function bar() {
if(!promiseCache.bar) {
promiseCache.bar = foo().then(doSomethingElseAsync);
}
return promiseCache.bar;
}
编辑:forceRefresh
特征
您可以通过传递(额外)参数强制函数刷新其缓存的承诺。
function foo(any, number, of, other, arguments, forceRefresh) {
if(forceRefresh || !promiseCache.foo) {
promiseCache.foo = doSomethingAsync();
}
return promiseCache.foo;
}
通过将 forceRefresh
作为最后一个参数,将其省略与传递 false
相同,并且 foo
将使用缓存的承诺(如果可用)。或者,传递 true
以保证调用 doSomethingAsync()
并刷新缓存值。
编辑 2:setName()/getName()
在 getName()
中使用 forceRefresh
机制:
setName(newName).then(getName.bind(null, true)); //set new name then read it back using forceRefresh.
或者,省略 forceRefresh
机制,假设缓存 属性 为 promiseCache.name
:
setName(newName).then(function() {
promiseCache.name = $.when(newName);//update the cache with a simulated `getName()` promise.
});
第一种方法更优雅,第二种方法更高效。
尝试使用可能的值 undefined
、 "pending"
、 true
创建一个对象 属性 ;当 obj.active
为 true
时调用 deferred.resolve()
,当 obj.active
为 "pending"
deferred.reject()
var res = {
active: void 0
};
var foo = function foo(state) {
var t;
var deferred = function(type) {
return $.Deferred(function(dfd) {
if (res.active === "pending" || state && state === "pending") {
res.active = "pending";
dfd.rejectWith(res, [res.active])
} else {
res.active = state || "pending";
t = setInterval(function() {
console.log(res.active)
}, 100);
setTimeout(function() {
clearInterval(t)
res.active = true;
dfd.resolveWith(res, [res.active])
}, 3000);
}
return dfd.promise()
})
.then(function(state) {
console.log("foo value", state);
return state
}, function(err) {
console.log("foo status", err)
return err
})
}
return deferred()
}
var bar = function bar(result) {
var deferred = function(type) {
return $.Deferred(function(dfd) {
if (result && result === true) {
setTimeout(function() {
dfd.resolveWith(result, [true])
}, 1500)
} else {
dfd.rejectWith(res, [res.active || "pending"])
};
return dfd.promise()
})
}
return deferred().then(function(data) {
console.log("bar value", data);
}, function(err) {
console.log("bar status", err);
})
}
$("button").click(function() {
$(this).is(":first")
? foo().then(bar, bar)
: bar(res.active === true ? res.active : "pending")
.then(foo, foo).then(bar, bar)
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js">
</script>
<button>foo</button>
<button>bar</button>
不确定我是否正确理解了问题。但这是我的看法:
将函数 foo 放入变量中
var foo_fn = function foo(foo_args){// Your async code goes here}
foo 是异步的,returns 在某些时候是异步的。在您对 foo 的定义中,我建议您使用 promises,该概念旨在以干净且可扩展的方式管理异步函数的组合。 jQuery 这个概念的实现在很多简单的用例中都很方便,但也有一些缺点,这让你在某些时候对使用遵循 Promises/A 规范的众多 promises 库之一很感兴趣。更多信息,您可以参考: 比照。 https://thewayofcode.wordpress.com/2013/01/22/javascript-promises-and-why-jquery-implementation-is-broken/ and https://blog.domenic.me/youre-missing-the-point-of-promises
所以,假设 foo 接受 args,returns 是一个承诺,后来解析为一些值。
var foo_fn = function foo(foo_args) { return foo_fn.promise = new RSVP.Promise(解决,拒绝){<br> // 你的异步代码在这里 } }
这里我使用 RSVP promise 库,但任何遵循 Promises/A 规范的 promise 库都可以完成这项工作。
当bar被调用时,你可以这样做:
function bar (bar_args) { var foo_promise = foo_fn.promise; // if foo was called, whether the computation is in progress or finished, // the foo_fn.promise field will be non-empty, as foo returns immediately // with a promise anytime it is called `` if (!foo.promise) { // foo has not yet been called so call it foo_promise = foo(foo_args); } foo_promise.then (function (foo_result) {/*some async code here*/}) }
注意:该解决方案与 Roamer-1888
提出的解决方案非常相似。一个区别是,在 Roamer 提案中,foo
函数在执行一次异步计算后将始终 return 相同的值。不知道这是否是预期的行为。在我的实现中,foo 执行异步。每次调用时计算。 bar
将使用存储在字段 foo_fn.promise
中的最新计算值。较早的计算已丢失,可能正在进行的计算未被考虑在内。
如果您打算在您的代码中经常使用此模式,您还可以创建一个在 define
模型上工作的函数
在 require.js
中发挥作用。
您将需要:
用于保存依赖项函数的注册表(在您的示例中为
foo
)依赖函数(在您的示例中为
bar
)将需要接受依赖函数计算值作为其签名的一部分。例如,依赖项的散列可以作为第一个参数传递,因此 bar 签名可以是:{foo: foo_result}, other_bar_args...
依赖函数必须遵循我之前回答的模型,即在执行时将它们的承诺值注册为属性。
提醒:您需要命名那些依赖函数以在其主体内引用它们,然后将该对象添加到注册表。
在 define
函数体中,将依赖函数包装到另一个函数中:
从注册表获取所有依赖项
获取所有依赖项值,必要时执行依赖项(类似于我之前的回答)。这意味着您最终会得到一个承诺列表,然后将其结果聚集在一起(
RSVP.hash
例如使用 RSVP 承诺库)。我相信 jQuery 与jQuery.when
具有相似的功能
您调用依赖函数 (
bar
) 并将此结果哈希作为第一个参数,其他参数与包装函数相同包装函数是新的
bar
,所以当调用bar
时,将调用包装函数。
有点长,但应该可以。如果您想查看一些代码,请告诉我这是否是您要找的。无论如何,如果你要有复杂的异步。在您的代码中,使用兼容的 promise 库对您来说可能会很有趣。 $.deferred 也只能在你看不到更好的东西时使用,因为它会让你更难跟踪你的函数的行为:你需要跟踪这个 deferred 似乎能够推理你的所有地方程序。