对JavaScript Promise/async/await的一些理解

new Promise executor的执行时机

new Promise里面通常会传入一个function参数。直觉上,传入的function,应该类似于callback,属于被异步调用的。但是实际上,它是同步被执行的。也就是说它是在Promise的构造函数里面被执行的。文献
https://javascript.info/promise-basics提到这个function有个专有的名字:executor:
The function passed to new Promise is called the executor. When new Promise is created, the executor runs automatically.
下面看看一个http request(https://developers.google.com/web/fundamentals/primers/promises)。new promise里面的XMLHttpRequest是即时发出的。所谓异步,体现在Promise.then的执行,是在onload/resolve之后发生的。

function get(url) {
  // Return a new promise.
  return new Promise(function(resolve, reject) {
    // Do the usual XHR stuff
    console.log("In Promise's executor");
    var req = new XMLHttpRequest();
    req.open('GET', url);

    req.onload = function() {
      // This is called even on 404 etc
      // so check the status
      if (req.status == 200) {
        // Resolve the promise with the response text
        resolve(req.status);
      }
      else {
        // Otherwise reject with the status text
        // which will hopefully be a meaningful error
        reject(Error(req.statusText));
      }
    };

    // Handle network errors
    req.onerror = function() {
      reject(Error("Network Error"));
    };

    // Make the request
    req.send();
  });
}
console.log("Begin");
get('https://jsfiddle.net/').then(function(response) {
  console.log("Success!", response);
}, function(error) {
  console.error("Failed!", error);
})

console.log("End");
Begin
In Promise's executor
End
Success! 200

Promise.then的执行时机

Promise.then里面的函数,其执行时机是主线程结束后的某个时间。
关于promise的执行有两种猜测:

  • chromium里面维护了多个线程,操作DOM所在的JS线程叫主线程。此外为了实现异步,准备了一个线程池,这个线程池,就是用于执行这里的promise的任务。
  • (作者更倾向于这个)Promise和操作DOM的JS都位于同一个主线程(我理解JS是单线程的,Promise也并没有和WebWorker绑定)。在Chromium内部,每次碰到Promise,都会给主线程post 一个task。chromium内部线程机制是,post task是在下一个时钟周期开始执行里面的任务的(每次时钟周期开始会切换任务队列)。所以我们能够观测到,所有Promise,都是在主线程把所有事情做完后的某个时机,才开始逐个被resolve的。要验证Promise是否是在主线程的方法是:在Promise里面执行一个超长的任务,看看网页UI是否失去响应(TODO)。
var promise1 = new Promise(function(resolve, reject) {
  // 输出1
  console.log("Inside new Promise, with 300 timeout");
  setTimeout(function() {
    // 输出4:延迟300ms后才去post resolve 任务。
    resolve('foo');
  }, 300);
});

promise1.then(function(value) {
  console.log(value);
});

var promise2 = new Promise(function(resolve, reject) {
  // 输出2
  console.log("Inside new Promise");
  // resolve并不会立刻输出bar。说明resolve仅仅向某个后台线程池或者主线程post了一个task。
  // 等这个主线程或者后台线程池将这个task执行结束,才会开始执行Promise.then里面的函数
  //输出3
  resolve('bar');
});

promise2.then(function(value) {
  console.log(value);
});
// 无论是否有console.log(promise1);,执行顺序不变。 
console.log(promise1);
// expected output: [object Promise]

输出是:

> "Inside new Promise, with 300 timeout"
> "Inside new Promise"
> [object Promise]
> "bar"
> "foo"

还有一个例证是,在整个while循环的执行时期,Promise.then里面的函数都没有被执行。

function testPromise2() {
  var promise1 = new Promise(function(resolve, reject) {
    // 输出1
    console.log("Inside new Promise, with 300 timeout");
    setTimeout(function() {
      // 输出6: 要等主线程结束后的300ms,才会去post resolve task。所谓主线程结束,就是输出End NOP。
      resolve('foo');
    }, 300);
  });

  promise1.then(function(value) {
    console.log(value);
  });

  var promise2 = new Promise(function(resolve, reject) {
    // 输出2
    console.log("Inside new Promise");
    // 输出5: 要等主线程结束后,才会去resolve。所谓主线程结束,就是输出End NOP。
    resolve('bar');
  });

  promise2.then(function(value) {
    console.log(value);
  });

  // 输出3
  console.log("Start NOP");
  let i =0;
  while (i < 1000000) {
    i++;
    let j =0;
    while (j < 10000) 
      j++;
  }
  // 输出4
  console.log("End NOP");
}

输出:

Inside new Promise, with 300 timeout
Inside new Promise
Start NOP
End NOP
bar
foo

http://es6.ruanyifeng.com/#docs/promise
https://stackoverflow.com/questions/32380344/async-update-dom-from-promises

await是同步还是异步

await会导致阻塞。但是这个阻塞的意思并不是阻塞整个async标记的函数,而是同一个async函数,在调用await之后的该函数的所有代码,会被阻塞。await之前的代码,以及async函数之外的代码,都是同步执行的。由于async function返回的是promise,所以await promise和await 一个async function没有区别。

async function f() {

  let promise = new Promise((resolve, reject) => {
    setTimeout(() => resolve("done!"), 3000)
  });

  let result = await promise; // wait until the promise resolves (*)
  console.log("Begin In promise");
  console.log(result);
  console.log("End In promise");
}

f();

下面这些信息都是在3秒之后输出的。这意味着await promise确实会导致阻塞。

Begin In promise
done!
End In promise

下面看看await 一个async function。

async function foo() {
  // 输出 2
  console.log("hi");
  return 1; 
}
async function bar() {
  // 输出 1,这里类似executor。
  console.log("before await foo, in bar");
  const result = await foo();
  // 输出 4,await之后的代码,不再属于executor。会被block执行。
  console.log("after await foo, in bar");
  console.log(result);
  console.log("after promise, in bar");
}
bar();
// 输出 3
console.log("lo"); 

对应的输出如下:

before await foo, in bar
hi
lo
after await foo, in bar
1
after promise, in bar

可以看到await确实是阻塞了。

引用例子:https://stackoverflow.com/questions/42773714/is-async-await-truly-non-blocking-in-the-browser
作者提到 “the code after await is never executed immediately”。这个说法其实不确切,要除掉await function里面的executor部分(如果async function里面有await,那么executor就是await之前的代码),await foo()会导致里面的console.log(“hi”)(executor)立即被执行。但是foo返回的promise,是在整个主线程执行结束之后开始执行的(console.log(result)输出1)。

async function foo() {
  //输出1,executor
  console.log("hi");
  return 1; 
}
async function bar() {
  const result = await foo();
  // 输出3
  console.log(result);
}
bar();
// 输出2
console.log("lo"); 

这段代码的输出是:

hi
lo
1

这说明,即使应用了await,foo里面的console.log(“hi”);仍然是同步的形式被首先执行。但是其返回值是promise,且await foo,所以await之后的代码console.log(result),有点类似被当作一个回调函数,被系统调度到了主任务(没有称主线程,按照文章开头的猜测,promise也可能位于主线程)结束之后开始执行。

在下面的函数bar里面,会等await foo()里面的代码完全结束后,console.log(result)才会有输出。即使console.log(result)修改为任意的console.log(“任意字符”),也是在await foo();完全结束后才会有任意字符的输出。同样的,console.log(“hi”)和waitNOP也是类似executor,会被立即执行。

function waitNOP() {
  let i =0;
  // 输出2
  console.log("waitNOP start"); 
  while (i < 1000000) {
    i++;
    let j =0;
    while (j < 10000) 
      j++;
  }
  // 输出3
  console.log("waitNOP end"); 
}

async function foo() {
 // 输出1
  console.log("hi");
  waitNOP();
  return 1; 
}
    
async function bar() {
  const result = await foo();
  // 输出5
  console.log(result);
}

 
bar();
//waitNOP();
// 输出4
console.log("lo"); 

输出结果:

hi
waitNOP start
waitNOP end
lo
1

下面的例子的console.log(“resolveAfter2Seconds end”); 会很早就执行。但是promise是2秒之后被执行的。这说明:await并不会是说,await的这个表达式里面的所有的内容都会等待。里面部分内容是立刻执行的。但是,await的返回时间,是里面的promise的状态变成resolved的时候。

等待是await promise导致的。

function resolveAfter2Seconds() {
  var promise = new Promise(resolve => {
    // setTimeout位于executor里面,会被立刻执行
    setTimeout(() => {
      // 输出3
      resolve('resolved');
    }, 2000);
  });
  // 输出2
  console.log("resolveAfter2Seconds end");   
  return promise;
}

async function asyncCall() {
  // 输出1
  console.log('calling');
  // await不会导致整个函数等待。等待的是promise的部分。
  var result = await resolveAfter2Seconds();
  // 下面的代码会被阻塞两秒。即使注释掉console.log(result);, console.log("asyncCall end") 也要2秒后执行
  console.log(result);
  console.log("asyncCall end"); 
  // expected output: 'resolved'
}
asyncCall();

输出结果:

> "calling"
> "resolveAfter2Seconds end"
> // 2秒后输出
> "resolved"
> "asyncCall end"

在上面的代码末尾加上一句console.log(“loo”);,会发生什么事情?
输出会是:

calling
 resolveAfter2Seconds end
loo
//下面的内容2秒后出现
resolved
asyncCall end

这说明await,只会阻塞他所在的async函数。调用它的sync函数(如果是个async函数调用它呢?),不会被阻塞。源码如下:

function resolveAfter2Seconds() {
  var promise = new Promise(resolve => {
    setTimeout(() => {
      resolve('resolved');
    }, 2000);
  });
  console.log("resolveAfter2Seconds end");   
  return promise;
}

async function asyncCall() {
  console.log('calling');
  // await不会导致整个函数等待。等待的是promise的部分。
  var result = await resolveAfter2Seconds();
  // 下面的代码会被阻塞两秒。即使注释掉console.log(result);, console.log("asyncCall end") 也要2秒后执行
  console.log(result);
  console.log("asyncCall end"); 
  // expected output: 'resolved'
}
asyncCall();
console.log("loo");

强制 await

下面这段代码,如果注释掉console.log(“lo”); ,其输出是hi,1。这个时候根本无法分辨这是同步还是异步。但是如果取消了注释,就变得有意思了,输出是hi,lo,1。这再次证明,await一个async函数,会导致这之后的代码被阻塞。

async function foo() {
  console.log("hi");
  return 1; 
}
async function bar() {
  const result = await foo();
  console.log(result);
}
bar();
//注释掉,输出是hi,1。没有注释,输出是hi,lo,1。
//console.log("lo"); 

全局处于Resolve状态的Promise不会导致阻塞

但是,局部的,仍然会阻塞。

async function foo() {
  // 输出1
  console.log("hi");
  return 1; 
}
    
async function bar() {
  const result = await foo();
  // 输出4:虽然result是处于Resolve状态,但是却是最后输出(局部的)。
  console.log(result);
  return 2;
}
 
let ansBar = bar();

// 输出2,全局的,“1”处于resolve状态,立刻输出。
console.log(ansBar); 
// 输出3
console.log("lo");

更新DOM UI

setTimeout不会阻塞主线程的UI。但是没有setTimeout的方式,会阻塞。
https://stackoverflow.com/questions/32380344/async-update-dom-from-promises

function test(i){
  return Promise.resolve()
  .then(function() {
    // update the DOM
    setTimeout(function() {
      document.getElementById("micro-out-div").innerHTML += i;
    }, 0);
    return i;
  });
}
function testBlock(i){
  return Promise.resolve()
  .then(function() {
    // update the DOM
    document.getElementById("micro-out-div").innerHTML += i;
    return i;
  });
}

var loadSequence = [];
// loop through all the frames!
for (var i = 0; i < 29999; i++) {
  //loadSequence.push(testBlock(i));
  loadSequence.push(test(i));
}

Promise.all(loadSequence).then(function(){
  window.console.log('all set...');
});

TFJS profile kernel

  profileKernel(name: string, f: () => T | Tensor[]):
      T {
    let result: T|Tensor[];
    const holdResultWrapperFn = () => {
      result = f();
    };
    const timer = this.backendTimer.time(holdResultWrapperFn);

    const results: Tensor[] =
        Array.isArray(result) ? result : [result] as Tensor[];
    results.forEach(r => {
      const vals = r.dataSync();
      util.checkComputationForErrors(vals, r.dtype, name);
      timer.then(timing => {
        let extraInfo = '';
        if (timing.getExtraProfileInfo != null) {
          extraInfo = timing.getExtraProfileInfo();
        }
        this.logger.logKernelProfile(name, r, vals, timing.kernelMs, extraInfo);
      });
    });

    return result as T;
  }
}

你可能感兴趣的:(Web)