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

Posted

技术标签:

【中文标题】如何在 Promise.then 中访问范围外的变量(类似于闭包)【英文标题】:How to access out of scope variables in Promise.then (similar to closure) 【发布时间】:2019-05-31 15:51:48 【问题描述】:

对此感到困惑,当然有一种优雅的方法可以做到这一点,但不确定是什么。

我想要类似的东西:

let x = 5;

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

x = 3;
// Print out 5 after 2 seconds.

基本上,给定与上述类似的设置,有没有办法打印出'5',而不管x 的值是否在异步超时期间更改?在我的情况下,很难在 resolve() 中简单地传递 x

【问题讨论】:

您可以通过不依赖全局状态来避免此类问题。 【参考方案1】:

您可以通过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;

之所以可行,是因为我们通过函数创建了一个作用域,该函数将变量 x 作为其参数之一绑定到传递给 IIFE 的任何值。

这允许我们将全局 x 绑定到其他东西,但在 IIFE 中绑定的 x 不受影响。

由于我们在 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;

注意:上述方法有效,因为我们正在处理原始值 which in javascript are immutable,因此每次重新分配都会重新创建一个新值。

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'

如果我们在处理对象,使用 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

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

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'

为了实现你对对象的需求,你必须模仿 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

或者使用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

注意:对象spread 和Object.assign 都执行浅克隆。深度克隆可以find many libraries on NPM。

见:What is the most efficient way to deep clone an object in 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

注意:使用 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);
  )

【讨论】:

很好的答案!不敢相信它没有更多的赞成票。谢谢你教我一些东西:)【参考方案2】:

是的,您可以使用工厂函数来生成可以充当变量闭包的 Promise。

function promiseFactory(x)
    return new Promise(function(resolve)
        setTimeout(function()
            console.log(x); // value as passed to factory call
             resolve(x)
        , 1000)
    );


let x = 5;
promiseFactory(x) // returns a promise which will always see x as 5
    .then(function(x)console.log(x))

一个小警告:这在这里有效,因为 x 是一个整数,它是一个原始类型,所以值会被复制过来。如果您使用像对象/数组这样的引用类型,则必须传递一个克隆对象

【讨论】:

以上是关于如何在 Promise.then 中访问范围外的变量(类似于闭包)的主要内容,如果未能解决你的问题,请参考以下文章

Promise.then(a, b) 和 Promise.then(a).catch(b) 一样吗? [复制]

如果使用 then ,是不是需要在 promise 中嵌套 catch?

RxJS 序列等价于 promise.then()?

Promise.then() 在 promise 解决之前执行

Promise.then方法的返回值问题

为啥在 React 组件中调用了两次 Promise.then 而不是 console.log?