简体   繁体   中英

Nesting asynchronous jquery promises

I've written a little program to help me understand how to do nested asynchronous promises in jquery.

I want to call two tasks, in sequence. The first task has two subtasks that also are called in sequence. That's all.

While this works, I have doubts as to whether this was the best way to use promises. Having to create a new deferred for every task feels like code smell to me. For example, would it be better to pass a single deferred object around and use only that? Thanks!

https://plnkr.co/edit/dyFFqqZhCVuhBYCuWjzm?p=preview

doTasks().then(function(arg) {
    console.log(arg)
})

function doTasks() {
    var d = $.Deferred();
    task1().then(function(arg) {
        console.log(arg)
        task2().then(function(arg) {
            console.log(arg)
            d.resolve('all tasks are done')
        })
    })
    return d.promise();
}

function task1() {
    var d = $.Deferred();

    console.log("starting task1...")

    setTimeout(function() {
        task1A().then(function() {
            task1B().then(function() {
                d.resolve('task1 is done')
            })
        })
    }, 10);

    return d.promise();
}

function task1A() {
    var d = $.Deferred();
    console.log("starting task1A...")
    setTimeout(function() {
        console.log("  resolving task1A...")
        d.resolve();
    }, 1000);
    return d.promise();
}

function task1B() {
    var d = $.Deferred();
    console.log("starting task1B...")
    setTimeout(function() {
        console.log("  resolving task1B...")
        d.resolve();
    }, 1000);
    return d.promise();
}

function task2() {
    var d = $.Deferred();
    console.log("starting task2...")

    setTimeout(function() {
        d.resolve('task2 is done');
    }, 1000);
    return d.promise()
}

Yes, it is a bit smelly to create a lot of unnecessary deferreds.

You don't need to create any deferreds for your ajax operations. If you return a promise from within a .then() handler, it is automatically chained to the previous promise (thus sequencing it) and all jquery ajax calls already return promises. So you don't need to create any of your own promises for the ajax operations. And, you can chain rather than nest in order to sequence your operations.

In fact, creating deferreds or promises when you don't need to is referred to as a promise anti-pattern . You want to use and return the promises that are already created rather than make new ones. And, where possible, you want to chain your promises rather than nest.

Here's how you can do that in a runnable snippet without creating a single deferred except one place to wrap setTimeout() in a promise. Obviously, you could use a lot less code for all your tasks if you created a re-usable function for them, but I'm assuming those are just placeholders for the real async operations so I'll leave them as is (except use the shared delay() function).

 doTasks().then(function(arg) { console.log("everything done"); }) function doTasks() { return task1().then(function(arg) { console.log(arg); return task2().then(function(arg) { console.log(arg) return arg; }) }) } // make promise version of setTimeout() in one central place // that can then be used elsewhere function delay(t) { return $.Deferred(function(def) { setTimeout(function() { def.resolve(); }, t); }).promise(); } function task1() { console.log("starting task1...") return delay(10).then(function() { return task1A(); }).then(function() { return task1B(); }); } function task1A() { console.log("starting task1A...") return delay(1000).then(function() { console.log(" resolving task1A...") return "done task1A"; }); } function task1B() { console.log("starting task1B...") return delay(1000).then(function() { console.log(" resolving task1B...") return "done task1B"; }); } function task2() { console.log("starting task2...") return delay(1000).then(function() { console.log(" task2 is done") return "done task2"; }); }
 <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

You should not nest your promises. The idea is that you chain them, by returning a value in the first then callback, and then chaining a next then that in its own callback will perform the next step, ...etc.

As you use a timer at several places to resolve a promise, you could create one function that would return such a promise based on a number of milliseconds.

You could even create a function that will output something and return again a promise.

That will allow you to chain the whole lot together:

 doTasks().then(say.bind(null, 'all done')); function say(msg) { console.log(msg); return $.Deferred().resolve(msg).promise(); } function doTasks() { return task1().then(task2); } function delay(ms) { // one function for delaying as a promise var d = $.Deferred(); setTimeout(d.resolve.bind(null, ' delay completed'), ms); return say('starting delay of ' + ms + ' milliseconds...') .then(d.promise).then(say); } function task1() { return say('starting task1...').then(delay.bind(null, 10)) .then(task1A).then(task1B).then(say.bind(null, 'task1 done')); } function task1A() { return say('starting task1A...').then(delay.bind(null, 1000)) .then(say.bind(null, ' resolving task1A...')); } function task1B() { return say('starting task1B...').then(delay.bind(null, 1000)) .then(say.bind(null, ' resolving task1B...')); } function task2() { return say('starting task2...').then(delay.bind(null, 1000)) .then(say.bind(null, 'task2 done')); }
 .as-console-wrapper { max-height: 100% !important; top: 0; }
 <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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