Javascript node.js 中的嵌套承诺是否正常?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/35805603/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 18:14:00  来源:igfitidea点击:

Are nested promises normal in node.js?

javascriptnode.jspromise

提问by Grim

The problem I have been struggling with for two weeks now while learning node.js is how to do synchronous programming using node. I found that no matter how I try to do things sequentiallyI always end up with nested promises. I have found that there are modules such as Q to help with promise chaining as far as maintainability.

在学习 node.js 时,我已经苦苦挣扎了两个星期的问题是如何使用 node.js 进行同步编程。我发现无论我如何尝试按顺序做事,我总是以嵌套的 promise 告终。我发现有诸如 Q 之类的模块可以在可维护性方面帮助承诺链接。

What I don't understand while doing research is Promise.all(), Promise.resolve()and Promise.reject(). Promise.rejectis pretty much self explanatory by the name but when writing an application I am confused on how to include any of these in functions or objects without breaking the behavior of the application.

我在做研究时不明白的是Promise.all(),Promise.resolve()Promise.reject()Promise.reject名称几乎不言自明,但是在编写应用程序时,我对如何在不破坏应用程序行为的情况下在函数或对象中包含任何这些内容感到困惑。

There is definitely a learning curve to node.js when coming from a programming language such as Java or C#. The question that still resides is if promise chaining is normal (best practice) in node.js.

当来自 Java 或 C# 等编程语言时,node.js 肯定有学习曲线。仍然存在的问题是 Promise 链在 node.js 中是否是正常的(最佳实践)。

Example:

例子:

driver.get('https://website.com/login').then(function () {
    loginPage.login('company.admin', 'password').then(function () {
        var employeePage = new EmployeePage(driver.getDriver());

        employeePage.clickAddEmployee().then(function() {
            setTimeout(function() {
                var addEmployeeForm = new AddEmployeeForm(driver.getDriver());

                addEmployeeForm.insertUserName(employee.username).then(function() {
                    addEmployeeForm.insertFirstName(employee.firstName).then(function() {
                        addEmployeeForm.insertLastName(employee.lastName).then(function() {
                            addEmployeeForm.clickCreateEmployee().then(function() {
                                employeePage.searchEmployee(employee);
                            });
                        });
                    });
                });
            }, 750);
        });
    });
});

回答by Tate Thurston

No, one of the great advantages of Promises is that you you can keep your async code linear rather than nested (callback hell from continuation passing style).

不,Promises 的一大优点是您可以保持异步代码线性而不是嵌套(来自连续传递风格的回调地狱)。

Promises give you return statements and error throwing, which you lose with continuation passing style.

Promise 为您提供 return 语句和错误抛出,您会因延续传递风格而失去这些。

You need to return the promise from your async functions so you can chain on the returned value.

您需要从异步函数返回承诺,以便您可以链接返回的值。

Here's an example:

下面是一个例子:

driver.get('https://website.com/login')
  .then(function() {
    return loginPage.login('company.admin', 'password')
  })
  .then(function() {
    var employeePage = new EmployeePage(driver.getDriver());
    return employeePage.clickAddEmployee();
  })
  .then(function() {
    setTimeout(function() {
      var addEmployeeForm = new AddEmployeeForm(driver.getDriver());

      addEmployeeForm.insertUserName(employee.username)
        .then(function() {
          return addEmployeeForm.insertFirstName(employee.firstName)
        })
        .then(function() {
          return addEmployeeForm.insertLastName(employee.lastName)
        })
        .then(function() {
          return addEmployeeForm.clickCreateEmployee()
        })
        .then(function() {
          return employeePage.searchEmployee(employee)
        });
    }, 750);
});

Promise.alltakes an array of promises and resolves once all promises resolve, if any are rejected, the array is rejected. This allows you to execute async code concurrently rather than serially, and still wait for the result of all concurrent functions. If you're comfortable with a threaded model, think spawning threads and then joining.

Promise.all接受一系列承诺并在所有承诺解决后解决,如果有任何承诺被拒绝,则该数组被拒绝。这允许您并发而不是串行执行异步代码,并且仍然等待所有并发函数的结果。如果您对线程模型感到满意,请考虑生成线程然后加入。

Example:

例子:

addEmployeeForm.insertUserName(employee.username)
    .then(function() {
        // these two functions will be invoked immediately and resolve concurrently
        return Promise.all([
            addEmployeeForm.insertFirstName(employee.firstName),
            addEmployeeForm.insertLastName(employee.lastName)
        ])
    })
    // this will be invoked after both insertFirstName and insertLastName have succeeded
    .then(function() {
        return addEmployeeForm.clickCreateEmployee()
    })
    .then(function() {
        return employeePage.searchEmployee(employee)
    })
    // if an error arises anywhere in the chain this function will be invoked
    .catch(function(err){
        console.log(err)
    });

Promise.resolve()and Promise.reject()are methods used when creating a Promise. They're used to wrap an async function using callbacks so that you can work with Promises instead of callbacks.

Promise.resolve()Promise.reject()是创建Promise. 它们用于使用回调包装异步函数,以便您可以使用 Promise 而不是回调。

Resolvewill resolve/fulfill the promise (this means a chained thenmethod will be called with the resulting value).
Rejectwill reject the promise (this means any chained thenmethod(s) will not be called, but the first chained catchmethod will be called with the error that arose).

Resolve将解决/实现承诺(这意味着then将使用结果值调用一个链式方法)。
Reject将拒绝承诺(这意味着任何链式then方法都不会被调用,但第一个链式catch方法将在出现错误的情况下被调用)。

I left your setTimeoutin place to preserve your programs behavior, but it's likely unnecessary.

我将您留setTimeout在原地以保留您的程序行为,但这可能是不必要的。

回答by Thalaivar

Use asynclibrary and use async.seriesinstead of nested chainings which looks really ugly and hard to debug/understand.

使用async库并使用async.series而不是看起来非常丑陋且难以调试/理解的嵌套链接。

async.series([
    methodOne,
    methodTwo
], function (err, results) {
    // Here, results is the value from each function
    console.log(results);
});

The Promise.all(iterable)method returns a promise that resolves when all of the promises in the iterable argument have resolved, or rejects with the reason of the first passed promise that rejects.

Promise.all(iterable)方法返回一个承诺,当可迭代参数中的所有承诺都已解决时,该承诺将解决,或者以第一个通过的承诺拒绝的原因拒绝。

var p1 = Promise.resolve(3);
var p2 = 1337;
var p3 = new Promise(function(resolve, reject) {
  setTimeout(resolve, 100, "foo");
}); 

Promise.all([p1, p2, p3]).then(function(values) { 
  console.log(values); // [3, 1337, "foo"] 
});

The Promise.resolve(value)method returns a Promise object that is resolved with the given value. If the value is a thenable (i.e. has a then method), the returned promise will "follow" that thenable, adopting its eventual state; otherwise the returned promise will be fulfilled with the value.

Promise.resolve(value)方法返回一个使用给定值解析的 Promise 对象。如果值是 thenable(即有 then 方法),则返回的 promise 将“跟随”该 thenable,采用其最终状态;否则返回的承诺将用该值实现。

var p = Promise.resolve([1,2,3]);
p.then(function(v) {
  console.log(v[0]); // 1
});

https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise/all

https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise/all

回答by Arun Sivasankaran

I removed the unnecessary nesting. Ill use syntax from 'bluebird'(my preferred Promise library) http://bluebirdjs.com/docs/api-reference.html

我删除了不必要的嵌套。我将使用来自“bluebird”(我首选的 Promise 库)的语法 http://bluebirdjs.com/docs/api-reference.html

var employeePage;

driver.get('https://website.com/login').then(function() {
    return loginPage.login('company.admin', 'password');
}).then(function() {
    employeePage = new EmployeePage(driver.getDriver());    
    return employeePage.clickAddEmployee();
}).then(function () {
    var deferred = Promise.pending();
    setTimeout(deferred.resolve,750);
    return deferred.promise;
}).then(function() {
    var addEmployeeForm = new AddEmployeeForm(driver.getDriver());
    return Promise.all([addEmployeeForm.insertUserName(employee.username),
                        addEmployeeForm.insertFirstName(employee.firstName),
                        addEmployeeForm.insertLastName(employee.lastName)]);
}).then(function() {
    return addEmployeeForm.clickCreateEmployee();
}).then(function() {
    return employeePage.searchEmployee(employee);
}).catch(console.log);

I modified your code to include examples for all you questions.

我修改了您的代码以包含所有问题的示例。

  1. There is no need to use the async library when working with promises. Promises are a very powerful by themselves and I think its an anti-pattern to mix promises and libraries like async.

  2. Normally you should avoid using the var deferred = Promise.pending() style...unless

  1. 使用 Promise 时无需使用异步库。Promise 本身就非常强大,我认为它是一种将 Promise 和库(如 async)混合在一起的反模式。

  2. 通常你应该避免使用 var deferred = Promise.pending() 样式......除非

'when wrapping a callback API that doesn't follow the standard convention. Like setTimeout:'

'当包装一个不遵循标准约定的回调 API 时。像 setTimeout:'

https://github.com/petkaantonov/bluebird/wiki/Promise-anti-patterns

https://github.com/petkaantonov/bluebird/wiki/Promise-anti-patterns

For the setTimeout example..create a 'deferred' promise...resolve the promise inside setTimeout and then return the promise outside setTimeout. This might seem a little unintuitive. Look at this example, I answered another question. Q.js promise with node. Missing error handler on `socket`. TypeError: Cannot call method 'then' of undefined

对于 setTimeout 示例……创建一个“延迟”承诺……在 setTimeout 内解析承诺,然后在 setTimeout 外返回承诺。这可能看起来有点不直观。看这个例子,我回答了另一个问题。 Q.js 承诺与节点。`socket` 上缺少错误处理程序。类型错误:无法调用未定义的方法“then”

Normally, you can get away with using Promise.promisify(someFunction) to convert a callback type function into a Promise returning function.

通常,您可以使用 Promise.promisify(someFunction) 将回调类型函数转换为 Promise 返回函数。

  1. Promise.all Lets say your are making multiple calls to an service that return asynchronously. If they don't depend on each other, you can make the calls simultaneously.
  1. Promise.all 假设您正在对异步返回的服务进行多次调用。如果它们不相互依赖,您可以同时拨打电话。

Just pass the function calls as an array. Promise.all([promiseReturningCall1, promiseReturningCall2, promiseReturningCall3]);

只需将函数调用作为数组传递即可。Promise.all([promiseReturningCall1, promiseReturningCall2, promiseReturningCall3]);

  1. Finally add a catch block to the very end..to make sure you catch any error. This will catch any exception anywhere in the chain.
  1. 最后在最后添加一个 catch 块..以确保您捕获任何错误。这将捕获链中任何地方的任何异常。

回答by Thank you

I just answered a similar questionwhere I explained a technique that uses generators to flatten Promise chains in a nice way. The technique gets its inspiration from coroutines.

我刚刚回答了一个类似的问题,其中我解释了一种使用生成器以一种很好的方式展平 Promise 链的技术。该技术的灵感来自协程。

Take this bit of code

拿这段代码

Promise.prototype.bind = Promise.prototype.then;

const coro = g => {
  const next = x => {
    let {done, value} = g.next(x);
    return done ? value : value.bind(next);
  }
  return next();
};

Using it, you can transform your deeply-nested Promise chain into this

使用它,你可以将你深嵌套的 Promise 链变成这个

coro(function* () {
  yield driver.get('https://website.com/login')
  yield loginPage.login('company.admin', 'password');
  var employeePage = new EmployeePage(driver.getDriver());
  yield employeePage.clickAddEmployee();
  setTimeout(() => {
    coro(function* () {
      var addEmployeeForm = new AddEmployeeForm(driver.getDriver());
      yield addEmployeeForm.insertUserName(employee.username);
      yield addEmployeeForm.insertFirstName(employee.firstName);
      yield addEmployeeForm.insertLastName(employee.lastName);
      yield addEmployeeForm.clickCreateEmployee();
      yield employeePage.searchEmployee(employee);
    }());
  }, 750);
}());

Using named generators, we can make that even more legible

使用命名生成器,我们可以使它更清晰

// don't forget to assign your free variables
// var driver = ...
// var loginPage = ...
// var employeePage = new EmployeePage(driver.getDriver());
// var addEmployeeForm = new AddEmployeeForm(driver.getDriver());
// var employee = ...

function* createEmployee () {
  yield addEmployeeForm.insertUserName(employee.username);
  yield addEmployeeForm.insertFirstName(employee.firstName);
  yield addEmployeeForm.insertLastName(employee.lastName);
  yield addEmployeeForm.clickCreateEmployee();
  yield employeePage.searchEmployee(employee);
}

function* login () {
  yield driver.get('https://website.com/login')
  yield loginPage.login('company.admin', 'password');
  yield employeePage.clickAddEmployee();
  setTimeout(() => coro(createEmployee()), 750);
}

coro(login());

However, this only scratches the surface of what's possible using coroutines to control the flow of promises. Read the answer I linked above that demonstrates some of the other advantages and capabilities of this technique.

然而,这只是触及了使用协程控制承诺流的可能性的皮毛。阅读我上面链接的答案,该答案展示了该技术的其他一些优点和功能。

If you do intend to use coroutines for this purpose, I encourage you to check out the co library.

如果您确实打算为此目的使用协程,我鼓励您查看co 库

Hope this helps.

希望这可以帮助。

PSnot sure why you're using setTimeoutin this fashion. What is the point of waiting for 750 ms specifically ?

PS不确定您为什么setTimeout以这种方式使用。特别是等待 750 毫秒有什么意义?

回答by Cymen

Your next step is to go from nesting to chaining. You need to realize that each promise is an isolated promise that can be chained in a parent promise. In other words, you can flatten the promises to a chain. Each promise result can be passed to the next one.

下一步是从嵌套到链接。您需要意识到每个承诺都是一个孤立的承诺,可以链接到父承诺中。换句话说,您可以将承诺扁平化为一个链。每个承诺结果都可以传递给下一个。

Here is a great blog post about it: Flattening Promise Chains. It uses Angular but you can ignore that and look at how a deep nesting of promises turns into a chain.

这是一篇关于它的很棒的博客文章:Flattening Promise Chains。它使用 Angular,但您可以忽略它并查看 Promise 的深层嵌套如何变成链。

Another good answer is right here on StackOverflow: Understanding javascript promises; stacks and chaining.

另一个很好的答案就在 StackOverflow 上:Understanding javascript promises; 堆栈和链接

回答by tubu13

You can chain promises like this:

你可以像这样链接承诺:

driver.get('https://website.com/login').then(function () {
    return loginPage.login('company.admin', 'password')
)}.then(function () {
    var employeePage = new EmployeePage(driver.getDriver());

    return employeePage.clickAddEmployee().then(function() {
        setTimeout(function() {
            var addEmployeeForm = new AddEmployeeForm(driver.getDriver());
        return addEmployeeForm.insertUserName(employee.username).then(function() {
                retun addEmployeeForm.insertFirstName(employee.firstName)
         }).then(function() {
                return addEmployeeForm.insertLastName(employee.lastName)
         }).then(function() {
             return addEmployeeForm.clickCreateEmployee()
         }).then(function () {
             retrun employeePage.searchEmployee(employee);
        })}, 750);
        });
    });
});

回答by shramee

Yeah like @TateThurston said, we chain them. It's even more aesthetically pleasing when you use es6 arrow functions

是的,就像@TateThurston 说的那样,我们将它们链接起来。使用 es6 箭头函数更美观

Here's an example:

下面是一个例子:

driver
    .get( 'https://website.com/login' )
    .then( () => loginPage.login( 'company.admin', 'password' ) )
    .then( () => new EmployeePage( driver.getDriver() ).clickAddEmployee() )
    .then( () => {
        setTimeout( () => {
            new AddEmployeeForm( driver.getDriver() )
                .insertUserName( employee.username )
                .then( () => addEmployeeForm.insertFirstName( employee.firstName ) )
                .then( () => addEmployeeForm.insertLastName( employee.lastName ) )
                .then( () => addEmployeeForm.clickCreateEmployee() )
                .then( () => employeePage.searchEmployee( employee ) );
        }, 750 )
    } );