如何在 Promise.then 中访问超出范围的变量(类似于闭包)

the*_*ura 6 javascript closures asynchronous promise

难倒这个,肯定有一种优雅的方式来做到这一点,但不确定是什么。

我想要类似的东西:

let x = 5;

const p = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve();
  }, 2000);
}).then(() => {
  console.log(x);
});

x = 3;
// Print out 5 after 2 seconds.
Run Code Online (Sandbox Code Playgroud)

基本上,给定与上述类似的设置,是否有办法打印输出,'5'而不管x在异步超时期间的值是否更改?就我而言,这将是很难简单地通过xresolve()

nem*_*035 8

您可以通过IIFE传递它:

let x = 5;

const p = (x => new Promise((resolve, reject) => {
//         ^ use it here
  setTimeout(() => {
    resolve();
  }, 2000);
}).then(() => {
  console.log(x);
}))(x);
//  ^ pass it here

x = 3;
Run Code Online (Sandbox Code Playgroud)

这样做的原因是因为我们通过函数创建一个作用域,该函数将变量x作为其参数之一绑定到传递到 IIFE 的任何值。

这允许我们将全局绑定x到其他东西,但xIIFE 内的边界不受影响。

由于我们在 IIFE 内部和外部都使用相同的名称,因此内部名称x也会遮盖外部名称。

也许使用不同的名称会让事情更具可读性:

let x = 5;

const p = (y => new Promise((resolve, reject) => {
//         ^ use it here under a different name
  setTimeout(() => {
    resolve();
  }, 2000);
}).then(() => {
  console.log(y);
}))(x);
//  ^ pass it here

x = 3;
Run Code Online (Sandbox Code Playgroud)


注意:上面的方法之所以有效,是因为我们处理的是原始值,这些值在 JavaScript 中是不可变的,因此每次重新赋值时都会重新创建一个新值。

var a = 'a'; 
var b = a; // this will bind `b` to the copy of value of `a` 
a = 'changed'; // this won't affect `b`
console.log(a, b); // 'changed', 'a'
Run Code Online (Sandbox Code Playgroud)

如果我们处理对象,使用 IIFE 是行不通的:

let x = { changed: false };

const p = (y => new Promise((resolve, reject) => {
//         ^ still points to the same object as x
  setTimeout(() => {
    resolve();
  }, 2000);
}).then(() => {
  console.log(y);
}))(x);

x.changed = true; // this will affect y as well
Run Code Online (Sandbox Code Playgroud)

原因是对象不是不可变的,因此每个绑定变量都指向同一个对象。

var a = { name: 'a' }; 
var b = a; // this will bind `b` to the value of `a` (not copy)
a.name = 'changed'; // this will also change `b`
console.log(a.name, b.name); // 'changed', 'changed'
Run Code Online (Sandbox Code Playgroud)

为了实现您对对象的需求,您必须模仿 JS 引擎对原语的操作,并在将对象传递到 IIFE 时克隆该对象:

let x = {
  changed: false
};

const p = (y => new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve();
  }, 2000);
}).then(() => {
  console.log(y);
}))({ ...x });
//  ^^^^^^^^ clone x when passing in

x.changed = true; // now this only affects the original, not the clone
Run Code Online (Sandbox Code Playgroud)

或者使用Object.assign

let x = {
  changed: false
};

const p = (y => new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve();
  }, 2000);
}).then(() => {
  console.log(y);
}))(Object.assign({}, x));
//  ^^^^^^^^^^^^^^^^^^^ clone x when passing in

x.changed = true; // now this only affects the original, not the clone
Run Code Online (Sandbox Code Playgroud)

注意:对象传播Object.assign执行浅克隆。对于深度克隆,您可以在 NPM 上找到许多库

请参阅:在 JavaScript 中深度克隆对象的最有效方法是什么?

对于大多数情况,这也可以工作:

let x = {
  changed: false
};

const p = (y => new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve();
  }, 2000);
}).then(() => {
  console.log(y);
}))(JSON.parse(JSON.stringify(x)));
//  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ clone x when passing in

x.changed = true; // now this only affects the original, not the clone
Run Code Online (Sandbox Code Playgroud)


注意:使用 IIFE 只是一个简单的示例。常规函数也可以工作(但对于非原始值仍然存在相同的问题):

let x = 5;

const p = createPromise(x);

x = 3;

function createPromise(y) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve();
    }, 2000);
  }).then(() => {
    console.log(y);
  })
}
Run Code Online (Sandbox Code Playgroud)