简体   繁体   English

Node.js 中的嵌套 promise 是否正常?

[英]Are nested promises normal in Node.js?

The problem I have been struggling with for two weeks now while learning Node.js is how to do synchronous programming using node.在学习 Node.js 时,我已经苦苦挣扎了两周的问题是如何使用 node 进行同步编程。 I found that no matter how I try to do things sequentially I always end up with nested promises.我发现无论我如何尝试按顺序做事,我总是以嵌套的 promises 告终。 I have found that there are modules such as Q to help with promise chaining as far as maintainability.我发现有诸如 Q 之类的模块可以在可维护性方面帮助承诺链。

What I don't understand while doing research is Promise.all() , Promise.resolve() and Promise.reject() .我在做研究时不明白的是Promise.all()Promise.resolve()Promise.reject() Promise.reject is 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.reject的名称几乎是不言自明的,但是在编写应用程序时,我对如何在函数或对象中包含任何这些而不破坏应用程序的行为感到困惑。

There is definitely a learning curve to Node.js when coming from a programming language such as Java or C#.从 Java 或 C# 等编程语言学习 Node.js 肯定有一个学习曲线。 The question that still resides is if promise chaining is normal (best practice) in Node.js.仍然存在的问题是,Promise 链在 Node.js 中是否正常(最佳实践)。

Example:例子:

driver.get('https://website.example/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);
        });
    });
});

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.example/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.all takes an array of promises and resolves once all promises resolve, if any are rejected, the array is rejected. Promise.all接受一组承诺,并在所有承诺解决后解决,如果有任何被拒绝,则该数组被拒绝。 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.如果您对线程模型感到满意,请考虑生成线程然后加入。

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 . Promise.resolve()Promise.reject()是创建Promise时使用的方法。 They're used to wrap an async function using callbacks so that you can work with Promises instead of callbacks.它们用于使用回调包装异步函数,以便您可以使用 Promises 而不是回调。

Resolve will resolve/fulfill the promise (this means a chained then method will be called with the resulting value). Resolve将解决/履行承诺(这意味着将使用结果值调用链接的then方法)。 Reject will reject the promise (this means any chained then method(s) will not be called, but the first chained catch method will be called with the error that arose). Reject将拒绝承诺(这意味着不会调用任何链接的then方法,但将调用第一个链接的catch方法并出现错误)。

I left your setTimeout in place to preserve your programs behavior, but it's likely unnecessary.我将您的setTimeout保留在适当的位置以保留您的程序行为,但这可能是不必要的。

Use async library and use async.series instead 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. Promise.resolve(value)方法返回一个使用给定值解析的 Promise 对象。 If the value is a thenable (ie has a then method), the returned promise will "follow" that thenable, adopting its eventual state;如果值是一个 thenable(即有一个 then 方法),返回的 promise 将“跟随”那个 thenable,采用它的最终状态; otherwise the returned promise will be fulfilled with the value.否则返回的 Promise 将用该值实现。

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

I just answered a similar question where I explained a technique that uses generators to flatten Promise chains in a nice way.我刚刚回答了一个类似的问题,我解释了一种使用生成器以一种很好的方式扁平化 Promise 链的技术。 The technique gets its inspiration from co-routines.该技术的灵感来自协同程序。

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.example/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.example/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 co-routines to control the flow of promises.然而,这只是触及了使用协程来控制 Promise 流的可能性的皮毛。 Read the answer I linked above that demonstrates some of the other advantages and capabilities of this technique.阅读我上面链接的答案,该答案展示了该技术的其他一些优点和功能。

If you do intend to use co-routines for this purpose, I encourage you to check out the co library .如果您确实打算为此目的使用协同例程,我鼓励您查看co 库

PS not sure why you're using setTimeout in this fashion. PS不知道你为什么以这种方式使用setTimeout What is the point of waiting for 750 ms specifically?具体等待750毫秒有什么意义?

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.example/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.使用 Promise 时无需使用异步库。 Promises are a very powerful by themselves and I think its an anti-pattern to mix promises and libraries like async. Promise 本身就非常强大,我认为混合 Promise 和诸如 async 之类的库是一种反模式。

  2. Normally you should avoid using the var deferred = Promise.pending() style...unless通常你应该避免使用 var deferred = Promise.pending() 风格...除非

'when wrapping a callback API that doesn't follow the standard convention. '当包装一个不遵循标准约定的回调 API 时。 Like setTimeout:'像 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.对于 setTimeout 示例..创建一个“延迟”承诺...解决 setTimeout 内的承诺,然后在 setTimeout 之外返回承诺。 This might seem a little unintuitive.这可能看起来有点不直观。 Look at this example, I answered another question.看这个例子,我回答了另一个问题。 Q.js promise with node. Q.js 承诺与节点。 Missing error handler on `socket`. `socket` 上缺少错误处理程序。 TypeError: Cannot call method 'then' of undefined TypeError:无法调用未定义的方法“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. Promise.all 假设您正在对异步返回的服务进行多次调用。 If they don't depend on each other, you can make the calls simultaneously.如果它们不相互依赖,您可以同时拨打电话。

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.最后在最后添加一个 catch 块..以确保您捕获任何错误。 This will catch any exception anywhere in the chain.这将捕获链中任何地方的任何异常。

Since this post is a top result for "nested promises" on Google, and having struggled with promises in my early days of learning node.js from a C# background, I thought I'd post something that would help others making a similar transition/evolution.由于这篇文章是谷歌上“嵌套承诺”的最佳结果,并且在我早期从 C# 背景学习 node.js 时一直在努力解决承诺,我想我会发布一些可以帮助其他人进行类似转换的东西/进化。

The voted-up answer by Tate is totally correct in that it does force a sequence, but the issue for most .NET or Java developers is that we're just not used to so many things being async operations in a synchronous language. Tate 投票赞成的答案是完全正确的,因为它确实强制了一个序列,但是对于大多数 .NET 或 Java 开发人员来说,问题是我们只是不习惯在同步语言中进行这么多的异步操作。 You have to be super-aware of what's async, because outer blocks continue & complete before any async action would.您必须非常了解什么是异步,因为外部块会在任何异步操作之前继续并完成。

To illustrate, here's some code (complete with nesting & two bugs!) I struggled with while learning promises with 'pg-promise':为了说明,这里有一些代码(完整的嵌套和两个错误!)我在学习“pg-promise”的承诺时遇到了困难:

            exports.create = async function createMeet(thingJson, res, next) {
    let conn;
    if (helpers.isDate(helpers.isStringDate(thingJson.ThingDate))){
        db.connect()
            .then(obj => {
                conn = obj;
                conn.proc('spCreateThing',[
                    thingJson.ThingName,
                    thingJson.ThingDescription,
                    thingJson.ThingDate])
                    .then(data => {
                        res.status(201).json(data);
                        res.send();
                    })
                    .catch(error =>{
                        console.error("Error creating a Thing via spCreateThing(" + thingJson + "): " + error);
                        next(createError(500, "Failed to create a Thing!"));
                    })
                    .finally(()  => {
                        conn.done(); //appropriate time to close the connection
                    });
                })
            .catch(error =>{
                console.error("Error establishing postgres database connection: " + error);
                next(createError(500, "Error establishing postgres database connection: " + error));
            })
            .finally(()  => { //this finally block will execute before the async actions fired in first .then() complete/start
                    conn.done(); //so this would close the connection before conn.proc() has completed/started
            });
        res.send(); //this will execute immediately following .connect() BEFORE any of the chained promise results,
        // thus sending a response before we've even figured out if the connection was successful and started the proc 
    } else {
        console.error("Attempt to create a Thing without valid date: " + thingJson.ThingDate);
        next(createError(400, "Must specify a valid date: " + thingJson.ThingDate));
    }

On top of that, the code that calls this function (ie a route handler) will complete before the db connection process even starts.最重要的是,调用此函数(即路由处理程序)的代码将在数据库连接过程开始之前完成。

So, the net of it is that outer functions define the promise structure and initiate the async calls, but then immediately complete their block since JS is first a synchronous language;因此,它的网络是外部函数定义了 promise 结构并启动异步调用,但随后立即完成它们的块,因为 JS 首先是一种同步语言; so be aware and assume all async calls don't even start until after the block that called it is complete.所以请注意并假设所有异步调用甚至在调用它的块完成之后开始

I know this is obvious to career JS developers (and is to me now), but I hope this really helps others new to these concepts.我知道这对职业 JS 开发人员来说是显而易见的(现在对我来说也是如此),但我希望这真的能帮助其他刚接触这些概念的人。

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.您需要意识到每个 Promise 都是一个孤立的 Promise,可以链接在父 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 .这是一篇很棒的博客文章: Flattening Promise Chains It uses Angular but you can ignore that and look at how a deep nesting of promises turns into a chain.它使用 Angular,但你可以忽略这一点,并看看一个深度嵌套的 Promise 是如何变成一个链的。

Another good answer is right here on StackOverflow: Understanding javascript promises;另一个很好的答案就在 StackOverflow 上: Understanding javascript promises; stacks and chaining . 堆栈和链接

You can chain promises like this:你可以像这样链接承诺:

driver.get('https://website.example/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);
        });
    });
});

Yeah like @TateThurston said, we chain them.是的,就像@TateThurston 说的,我们把它们锁起来。 It's even more aesthetically pleasing when you use es6 arrow functions 😋当你使用 es6 箭头函数时,它更美观😋

Here's an example:这是一个例子:

driver
    .get( 'https://website.example/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 )
    } );

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM