Can Promise load multi urls in order?

2019-01-27 06:52发布

问题:

promise then return a specified promise gives the following code snippet:

function get(url) {
  return new Promise(function(resolve, reject) {
    var req = new XMLHttpRequest();
    req.open('GET', url);

    req.onload = function() {
      if (req.status == 200) {
        resolve(req.response);
      }
      else {
        reject(Error(req.statusText));
      }
    };
    req.onerror = function() {
      reject(Error("Network Error"));
    };
    req.send();
  });
}

This function can be used as following:

get('story.json').then(function(response) {
  console.log("Success!", response);
}, function(error) {
  console.error("Failed!", error);
});

I want to use promise to load multi urls, such as

get('a.json').get('b.json')
// or get('a.json').then().get('b.json')

I have implemented it in other ways. But as I understand it, Promise can not do this work. Really ?

Add

In fact, I implemented a similar lib that help execute dynamic scripts in browsers:

Loader = (function() {

  var load_cursor = 0;
  var load_queue;

  var loadFinished = function() {
    load_cursor ++;
    if (load_cursor < load_queue.length) {
      loadScript();
    }
  }

  function loadError (oError) {
    console.error("The script " + oError.target.src + " is not accessible.");
  }


  var loadScript = function() {
    var url = load_queue[load_cursor];
    var script = document.createElement('script');
    script.type = "text/javascript";

    if (script.readyState){  //IE
        script.onreadystatechange = function(){
            if (script.readyState == "loaded" ||
                    script.readyState == "complete"){
                script.onreadystatechange = null;
                loadFinished();
            }
        };
    } else {  //Others
        script.onload = function(){
            loadFinished();
        };
    }

    script.onerror = loadError;

    script.src = url+'?'+'time='+Date.parse(new Date());
    document.body.appendChild(script);
  };

  var loadMultiScript = function(url_array) {
    load_cursor = 0;
    load_queue = url_array;
    loadScript();
  }

  return {
    load: loadMultiScript,
  };

})();  // end Loader


// load...

Loader.load(['http://cdnjs.cloudflare.com/ajax/libs/jquery/2.0.0/jquery.min.js', './my.js']);

回答1:

Using Normal Promise Syntax

Chain Promises

You can chain your promises if you want them sequential:

get('a.json').then(function(a) {
    return get('b.json');
}).then(function(b){
    return get('c.json');
}).then(function(c) {
    // all done here
}), function(err) {
    // error here
});

Or in ES7, you can use async/await like this:

async function someFunction() {
    let a = await get('a.json');
    let b = await get('b.json');
    // do something with a and b here
    return something;
}

someFunction().then(result => {
    // all done here
}).catch(err => {
    // error here
});

Run Promises in Parallel

If you want them loaded in parallel, you can use Promise.all():

Promise.all([get('a.json'), get('b.json'), get('c.json')]).then(function(results) {
    // all done with results here
}, function(err) {
    // error here
});

Sequence Using .reduce()

Or, if you use the same code to process each result, you can load them sequentially using the reduce() design pattern:

['a.json', 'b.json', 'c.json'].reduce(function(p, item) {
    return p.then(function(){
        // process item here
    });
}, Promise.resolve()).then(function(result) {
    // all done here
}, function(err) {
    // error here
});

Sequence Using Bluebird's .map()

Or, if using the Bluebird promise library, it has Promise.map() which is very useful for parallel operations on an array

Promise.map(['a.json', 'b.json', 'c.json'], function(item) {
    // do some operation on item and return data or a promise
    return something;
}).then(function(results) {
    // all done
}, function(err) {
    // error here
});

Making get(x).get(y).get(z) Work

Extending the Promise

I was intrigued by the get(x).get(y).get(z) question using promises. I conjured up a way to do that in this working snippet:

function get(url) {
    function pget(u) {
        var p = this.then(function (result) {
            log(result);
            return get(u);
        });
        p.get = pget;
        return p;
    }
    var p = new Promise(function (resolve, reject) {
        setTimeout(function() {
            resolve(url);
        }, 500);
    });
    p.get = pget;
    return p;
}

get('a.json').get('b.json').get('c.json').then(function(result) {
    log(result);
    log("done");
}, function(err) {
    log("err");
});

function log(x) {
    var div = document.createElement("div");
    div.innerHTML = x;
    document.body.appendChild(div);
}

This is a bit of a hack. I think the standards body is working on a more official way to extend a promise like this that will work in more cases than this will. But, I tried it in Chrome, Firefox and IE with Bluebird and this particular usage works in all three. The challenge with this method is that every .then() creates a new promise and, by default, it will not have your .get() method on it. We get away with it here because of the way it is used, but you would have to be careful exactly how you use this.

Chaining Our Own Object

Here's a bit different way to do the get(x).get(y).get(z) syntax. It requires a blank .get() at the end to tell you it want to stop the chain and get access to the final promise. The advantage of this scheme is that it doesn't mess with the promise object in any way.

function delay(url) {
    return new Promise(function (resolve, reject) {
        setTimeout(function() {
            log(url);
            resolve(url);
        }, 500);
    });
}

function get(url) {
    
    function pget(u) {
        if (!u) {
            return this.promise;
        } else {
            return {promise: this.promise.then(function() {
                return delay(u);
            }), get: pget}
        }
    }
    
    return {promise: delay(url), get: pget};
}

// note the empty .get() right before the .then()
// the empty .get() at the end is required in this scheme
get('a.json').get('b.json').get('c.json').get().then(function(result) {
    log("done");
}, function(err) {
    log("err");
});


function log(x) {
    var div = document.createElement("div");
    div.innerHTML = x;
    document.body.appendChild(div);
}



回答2:

You can join promises , in that case you have to return next promise from success callback

get('story.json').then(function(response) {
     console.log("Success!", response);
    return get('b.json');
   }, function(error) {
   console.error("Failed!", error);
}).then(function(res){
 // response for b
});