JavaScript Promise是异步的吗?

28

只是一个澄清的问题:JavaScript中的Promise是异步的吗?我一直在读很多有关Promise和异步编程(即ajax请求)的文章。如果Promise不是异步的,我们该怎么办呢?

例如,我有一个函数用来包装一个带有参数数组args的函数f到一个Promise里面。没有什么关于f本质上是异步的。

function getPromise(f, args) {
 return new Promise(function(resolve, reject) {
  var result = f.apply(undefined, args);
  resolve(result);
 });
}
为了让这段代码变成异步的,我查了一些 Stack Overflow 的文章,并决定使用 setTimeout,因为很多人都推荐它来使代码非阻塞。
function getPromise(f, args) {
 return new Promise(function(resolve, reject) {
  setTimeout(function() { 
   var r = f.apply(undefined, args);
   resolve(r);
  }, 0);
 });
}

使用setTimeout的这种方式,能否使代码在Promise内部非阻塞?

(请注意,我不依赖于任何第三方的Promise API,只使用浏览器支持的API)。


1
https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise - Robby Cornelissen
4个回答

40

我认为你有一个误解。JavaScript 代码始终是阻塞的,因为它运行在单线程上。但 JavaScript 异步编码的优势在于,像 I/O 这样的外部操作不需要阻塞该线程。处理来自 I/O 的响应的回调仍然是阻塞的,没有其他 JavaScript 可以同时运行。

现在回答你具体的问题:

简单澄清一下:JavaScript Promise 是异步的吗?

不是,传递到 Promise 构造函数中的回调会立即同步执行,但是确实可以启动一项异步任务(例如设置超时或写入文件),并等待该异步任务完成后再解决 Promise,事实上这是 Promise 的主要用例。

使用 setTimeout 的这种方法是否可使 Promise 中的代码非阻塞?

不是,它只是改变了执行顺序。您的脚本将继续执行直到完成,然后当它无事可做时,setTimeout 的回调才会执行。

澄清一下:

    console.log( 'a' );
    
    new Promise( function ( ) {
        console.log( 'b' );
        setTimeout( function ( ) {
            console.log( 'D' );
        }, 0 );
    } );

    // Other synchronous stuff, that possibly takes a very long time to process
    
    console.log( 'c' );

以上程序会以确定性的方式打印:

a
b
c
D

这是因为 setTimeout 的回调函数只有在主线程没有其他任务要执行时才会被执行(在记录“c”之后)。


我现在有点明白了。我想(除非我费尽心思去了解WebWorkers),使用Promise不会使我的代码非阻塞或异步。基本上,我将继续使用Promise,因为现在我没有嵌套的回调处理。谢谢。 - Jane Wayne
11
我知道这个答案是一段时间之前写的,但我想知道是否有任何参考资料?根据MDN的说法,Promise是异步的:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise。 - Chris Paton
2
我也在努力弄清楚Promise是异步还是同步的。参考 - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises“本质上,Promise是一个返回的对象,您可以将回调附加到该对象,而不是将回调传递到函数中。”我理解Promise本身是同步的,但它通常与异步函数一起使用。因此,人们说 - “Promise是表示异步操作的最终完成或失败的对象”其中的示例包括异步和普通函数。 - Sachin Ramdhan Boob
如果 Promise 不是异步的,那么为什么它可以使用 await 关键字呢?例如:await new Promise... - variable
2
@ChrisPaton 我认为 MDN 在描述 Promise 的行为时有点粗心:“注意,Promise 保证是异步的。” 它应该说:“请注意,解决 Promise 是保证异步的。” 就像答案中构造函数的执行者一样,在同步情况下会被调用。 - funct7
显示剩余2条评论

2

我知道这是七年前的问题,但是:

答案

  • Promise块是同步的
  • Promise回调块(resolve、reject)是同步的
  • 如果Promise块使用setTimeout块,回调call(而不是块本身)可以是异步的

因此,直接回答Jane Wayne提出的问题,是的,使用setTimeout块将使Promise块非阻塞。

解释

Javascript是同步的,它只有一个线程。Javascript引擎使用堆栈(Call Stack)来执行所有的东西(LIFO)。

在旁边,你有Web APIs,它们是浏览器的一部分。例如,你可以在Window对象中找到setTimeout

两个世界之间的通信是通过使用2个队列(微任务和宏任务队列,它们是FIFO)由Event Loop管理的,它是一个不断运行的进程。

事件循环、微任务和宏任务队列

执行过程如下:

  • 使用调用栈执行所有脚本
  • 事件循环一直检查调用栈是否为空
  • 如果事件循环发现调用栈为空,则开始将微任务队列中的回调(如果有)传递到调用栈
  • 一旦微任务队列为空,事件循环会对宏任务队列执行相同的操作

因此,进入Promise世界。

  • Promise 块是同步执行的。
  • Promise 块内,您可以有一些在 JavaScript 引擎之外执行的东西(例如 setTimeout 块),这就是我们所谓的异步,因为它在 JavaScript 线程之外运行。
  • Promise 的回调函数(resolvereject)的执行可以被视为异步,但块本身是同步执行的。

因此,如果在 Promise 块内没有任何异步块,则 Promise 会阻止您的执行。 如果在回调部分中没有任何异步块,则 Promise 将阻止您的执行。

示例:

console.log(`Start of file`)

const fourSeconds = 4000

// This will not block the execution
// This will go to the Macrotask queue
// Microtask queue has preference over it, so all the Promises not using setTimeout
setTimeout(() => {
    console.log(`Callback 1`)
})

// This will block the execution because the Promise block is synchronous
// But the result will be executed after all the Call Stack
// This will be the first Microtask printed
new Promise((resolve, reject) => {
    let start = Date.now()
    while (Date.now() - start <= fourSeconds) { }
    resolve(`Promise block resolved`)
}).then(result => console.log(result))

// This will not block the execution
// This will go into the Macrotask queue because of setTimeout
// Will block all the Macrotasks queued after it
// In this case will block the Macrotask "Callback 2" printing
new Promise((resolve, reject) => {
    setTimeout(() => resolve(`Promise callback block resolved`))
}).then(result => {
    let start = Date.now()
    while (Date.now() - start <= fourSeconds) { }
    console.log(result)
})

// This will not block the execution
// This will go to the Macrotask queue
// Microtask queue has preference over it
// Also the previous Macrotasks has preference over it, so this will be the last thing to be executed
setTimeout(() => {
    console.log(`Callback 2`)
})

// This will not block the execution
// It will go to the Microtask queue having preference over the setTimeout
// Also the previous Microtasks has preference over it
Promise.resolve(`Simply resolved`).then(result => console.log(result))

console.log(`End of file`)

/*

Output:
 
Start of file
[... execution blocked 4 seconds ...]
End of file
Promise block resolved
Simply resolved
Callback 1
[... execution blocked 4 seconds ...]
Promise callback block resolved
Callback 2

*/

1

const p = new Promise((resolve, reject) => {
  if (1 + 1 === 2) {
    resolve("A");
  } else {
    reject("B");
  }
});

p.then((name) => console.log(name)).catch((name) => console.log(name));
console.log("hello world");

Promise在等待状态时不会阻塞下一行代码的执行。因此,它是异步执行的。


0

你的 MDN 参考资料很有帮助。谢谢。

如果你运行这个程序,你应该会看到异步输出。

================================================================ 使用 "Promise" 进行异步操作

const log = console.log;

//---------------------------------------

class PromiseLab {

    play_promise_chain(start) {
        //"then" returns a promise, so we can chain
        const promise = new Promise((resolve, reject) => {      
            resolve(start);
        });     
        promise.then((start) => {
            log(`Value: "${start}" -- adding one`);
            return start + 1;
        }).then((start) => {
            log(`Value: "${start}" -- adding two`);
            return start + 2;
        }).then((start) => {
            log(`Value: "${start}" -- adding three`);
            return start + 3;
        }).catch((error) => {
            if (error) log(error);
        });             
    }
        
}

//---------------------------------------

const lab = new PromiseLab();
lab.play_promise_chain(100);
lab.play_promise_chain(200);

输出应该是异步的,类似于:

Value: "100" -- adding one
Value: "200" -- adding one
Value: "101" -- adding two
Value: "201" -- adding two
Value: "103" -- adding three
Value: "203" -- adding three

================================================================ 使用"MyPromise"同步方式(例如基本的JavaScript代码)

const log = console.log;

//---------------------------------------

class MyPromise {
    
    value(value) { this.value = value; }
    
    error(err) { this.error = err; }
    
    constructor(twoArgFct) {
        twoArgFct(
            aValue => this.value(aValue),
            anError => this.error(anError));    
    }
    
    then(resultHandler) { 
        const result = resultHandler(this.value);
        return new MyPromise((resolve, reject) => {     
            resolve(result);
        });
    }
    
    catch(errorHandler) { 
        errorHandler(this.error());
    }
    
}

//--------------------------------------
    
class MyPromiseLab {

    play_promise_chain(start) {
        //"then" returns a promise, so we can chain
        const promise = new MyPromise((resolve, reject) => {        
            resolve(start);
        });     
        promise.then((start) => {
            log(`Value: "${start}" -- adding one`);
            return start + 1;
        }).then((start) => {
            log(`Value: "${start}" -- adding two`);
            return start + 2;
        }).then((start) => {
            log(`Value: "${start}" -- adding three`);
            return start + 3;
        }).catch((error) => {
            if (error) log(error);
        });             
    }
        
}

//---------------------------------------

const lab = new MyPromiseLab();
lab.play_promise_chain(100);
lab.play_promise_chain(200);

输出应该是同步的:

Value: "100" -- adding one
Value: "101" -- adding two
Value: "103" -- adding three
Value: "200" -- adding one
Value: "201" -- adding two
Value: "203" -- adding three

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接