从一个非常简单的例子开始,要求用户确认一个选择:
let a = function(){ return window.confirm( "Proceed" ); };
let b = function(){ console.log("b: " + a() ); };
let c = function(){ b(); console.log( "after b" ); };
c();
c();
window.confirm()
是一个异步事件,但该函数以同步方式运行并阻止执行,直到它已解决。
将此更改为使用自定义 UI,该 UI 使用
await
阻止执行,直到用户响应。但是await
文档指出:
运算符用于等待await
。它只能在Promise
函数中使用。async
所以包含
await
表达式的函数必须做async
并且返回一个promise,而不是之前的值,现在a()
不会阻塞执行:
let a = async function(){
let conf = document.createElement( "DIV" );
conf.className = 'confirm';
let message = document.createElement( "DIV" );
message.className = 'confirm-message';
message.innerText = 'Proceed';
conf.appendChild( message );
let ok = document.createElement( "BUTTON" );
ok.innerText = 'Ok';
conf.appendChild( ok );
let cancel = document.createElement( "BUTTON" );
cancel.innerText = 'Cancel';
conf.appendChild( cancel );
document.body.appendChild( conf );
let v = await new Promise(
response => {
ok .addEventListener( "click", ()=>{ response(true ); }, false );
cancel.addEventListener( "click", ()=>{ response(false); }, false );
}
);
conf.remove();
return v;
};
let b = function(){ console.log("b: " + a() ); };
let c = function(){ b(); console.log( "after b" ); };
c();
c();
.confirm { display: grid; width: min-content; grid-template-columns: 10em 10em; grid-gap: 0.5em; grid-auto-rows: 1.4em; text-align: center; border: 1px #777 solid; background-color: #AAA; padding: 0.5em; }
.confirm > .confirm-message { grid-column: 1 / 3; }
这只是将问题移到调用堆栈上,后续函数需要使用
await
/async
以确保它们等到用户输入已解决:
let a = async function(){
let conf = document.createElement( "DIV" );
conf.className = 'confirm';
let message = document.createElement( "DIV" );
message.className = 'confirm-message';
message.innerText = 'Proceed';
conf.appendChild( message );
let ok = document.createElement( "BUTTON" );
ok.innerText = 'Ok';
conf.appendChild( ok );
let cancel = document.createElement( "BUTTON" );
cancel.innerText = 'Cancel';
conf.appendChild( cancel );
document.body.appendChild( conf );
let v = await new Promise(
response => {
ok .addEventListener( "click", ()=>{ response(true ); }, false );
cancel.addEventListener( "click", ()=>{ response(false); }, false );
}
);
conf.remove();
return v;
};
let b = async function(){ let v = await a(); console.log("b: " + v ); };
let c = async function(){ let v = await b(); console.log( "after b" ); };
(async function(){
let c1 = await c();
let c2 = await c();
})();
.confirm { display: grid; width: min-content; grid-template-columns: 10em 10em; grid-gap: 0.5em; grid-auto-rows: 1.4em; text-align: center; border: 1px #777 solid; background-color: #AAA; padding: 0.5em; }
.confirm > .confirm-message { grid-column: 1 / 3; }
有没有办法使用新的
await
语法来阻止执行而不更改所有调用函数的签名并使它们成为async
(模仿使用内置window.confirm()
函数的示例)?
如果我理解正确,您想在顶级/全局上下文中阻止执行。 顶级 await 仅在模块中可用。除非你使用模块,你不能在全局上下文中阻止执行(即使那样,它也只会为那个模块被阻止)。
您可以做的下一个最好的事情是创建一种
main
异步函数,您可以在其中放置所有您希望阻止的代码。现在,如果您将 a()
、b()
和 c()
放入此函数中,当使用 await
时,此函数中的所有其他代码都将被阻止。 main
函数将返回一个 Promise
并运行 async,但是,由于您的所有代码都在这个函数中,在它内部,它是同步的。让 main
函数阻塞整个 JavaScript 线程是不可能的。请注意,这需要重新编写您的函数才能使用 async/await
.
此解决方案涉及将问题尽可能多地移动到调用堆栈的顶部,如果您想阻止其他代码,这是唯一的解决方案。请注意,您也不能阻止页面上的其他视觉事件,例如聚焦输入、单击按钮、触发 CSS 悬停动画等,而不使用额外的 JS 在提示打开时取消这些事件或使用某种覆盖/背景。
这里有一些例子(我使用了箭头函数等 ES 特性,但如果这对你的项目不起作用,请随意更改):
(async () => {
const a = () {
// … render HTML etc.
return await Promise(resolve => { /* … add event listeners */}
}
const v = await a();
console.log("value: ", v);
console.log("end");
})();
(async () => {…})()
是您的主要功能,您可以在其中模拟同步执行(如果您要阻止的所有代码都在该功能内)
如果你想使用
b()
和c()
,同样你可以:
(async () => {
const a = async () {
// … render HTML etc.
return await Promise(resolve => { /* … add event listeners */}
}
const b = async () {console.log("value: ", await a())};
const c = async () {await b(); console.log("end");
await c();
// any other after this line is blocked until resolution of `c()`
})();
演示:
(async () => {
const a = async () => {
const conf = document.createElement('div');
conf.className = 'confirm';
const message = document.createElement('div');
message.className = 'confirm-message';
message.textContent = 'Proceed';
conf.appendChild(message);
const ok = document.createElement('button');
ok.textContent = 'Ok';
conf.appendChild(ok);
const cancel = document.createElement('button');
cancel.textContent = 'Cancel';
conf.appendChild(cancel);
document.body.appendChild(conf);
const v = await new Promise(resolve => {
ok.addEventListener('click', () => resolve(true), false);
cancel.addEventListener('click', () => resolve(false), false);
});
conf.remove();
return v;
};
const b = async () => console.log("b: " + await a());
const c = async () => {
await b();
console.log( "after b" );
};
await c();
// delay of 1 second
await new Promise(resolve => setTimeout(resolve, 1000));
console.log("2nd go");
await c();
})();
.confirm { display: grid; width: min-content; grid-template-columns: 10em 10em; grid-gap: 0.5em; grid-auto-rows: 1.4em; text-align: center; border: 1px #777 solid; background-color: #AAA; padding: 0.5em; }
.confirm > .confirm-message { grid-column: 1 / 3; }
另一个选择当然是简单地使用一个module。像 Webpack 这样的第三方库通过将所有代码放在异步函数中来实现顶级等待,类似于我的演示。
总结:
在浏览器 JavaScript 中。那将是非常糟糕的,并且允许您“冻结”用户的选项卡,甚至不允许他们关闭它。模块中的顶级 await 可以进行阻塞。要模拟阻塞,您可以将所有代码放在异步上下文中;在这种情况下,任何
await
都会阻塞整个上下文。
在 Node.js 中,新版本支持顶级等待,您可以阻止 Node.js 应用程序的全局上下文。因此,如果您使用受支持的 Node.js 版本,则不需要添加像
(async () => {…})();
这样的包装器。
您应该使用
then()
功能。然后在履行承诺后返回异步函数的结果。
// First define your main async function.
let a = async function(){
let v = await new Promise(
response => {
ok .addEventListener( "click", ()=>{ response(true ); }, false );
cancel.addEventListener( "click", ()=>{ response(false); }, false );
}
);
return v;
};
// Then get the results of your async function
let b = a.then((result) => {
console.log("b: " + result );
});
当
async
函数到达 await
语句时,函数的 caller 将继续执行,如果它不使用 await
也暂停执行。
这使您可以控制程序将在哪个级别停止并等待。
既然你想让它停在顶层,就无法避免在调用堆栈的所有函数中添加
await
/async
。