jQuery 延期承诺乱序执行?
jQuery deferred promises executing out of order?
编辑:重要说明这是使用 jQuery 1.7.2,不,不能从这个版本更改
我对 promises 不熟悉,并试图全神贯注于它们。我正在尝试按顺序执行一系列功能,等待它们完成后再创建一些子视图(在 Backbone.js 中)。这是我的代码:
initialize: function () {
console.log('AppView::initialized!');
var _this = this;
$.when( _this.processCookies() )
.then( _this.loadAdScripts() )
.then( _this.createChildViews() );
},
processCookies: function () {
var def = $.Deferred();
console.log('(1) PROCESS COOKIES');
return def.resolve();
},
/**
* Instantiates new instances of the child views.
*/
createChildViews: function () {
var _this = this;
console.log('(4) CREATING CHILD VIEWS');
},
loadAdScripts: function () {
var _this = this,
def = $.Deferred();
$.when(
_this.insertScript({
name: 'example1',
async: false,
src: '//www.example.com/script1.js',
}),
_this.insertScript({
is_mobile: is_mobile,
name: 'example2',
async: true,
src: '//example.com/script2.js'
})
)
.done(function () {
console.log('(3) ALL SCRIPTS LOADED');
def.resolve();
});
},
insertScript: function (script) {
var def = $.Deferred(),
protocol = (document.location.protocol === 'https:' ? 'https:' : 'http:');
// dont script 2 on mobile.
if (script.name === 'example2' && script.is_mobile) {
console.log('skipping script');
return def.resolve();
}
var promise = $.ajax({
dataType: 'script',
cache: false,
async: script.async,
url: protocol + script.src,
});
promise.done( function () {
console.log('(2) SINGLE SCRIPT LOADED');
return def.resolve();
});
},
因此,此处所需的流程是:
- 当
processCookies()
函数完成后,
- 执行
loadAdScripts
函数
2a. insertScript()
触发,脚本 1 加载
2b. insertScript()
触发,脚本 2 加载
- 两个脚本都完成后,执行
createChildViews
函数。
所以,观察代码中的 console.log()
占位符,我 期望 在我的控制台中看到:
'(1) PROCESS COOKIES'
'(2) SINGLE SCRIPT LOADED'
'(2) SINGLE SCRIPT LOADED'
'(3) ALL SCRIPTS LOADED'
'(4) CREATING CHILD VIEWS'
然而我实际上看到的是:
'(1) PROCESS COOKIES'
'(3) ALL SCRIPTS LAODED'
'(4) CREATING CHILD VIEWS'
'(2) SINGLE SCRIPT LOADED'
'(2) SINGLE SCRIPT LOADED'
我的承诺有什么问题,为什么它们没有按预期的顺序执行?
$.when( _this.processCookies() )
.then( _this.loadAdScripts() )
.then( _this.createChildViews() );
似乎正在立即调用 loadScripts()
、 createChildViews()
,而是尝试在 .then(_this.loadAdScripts)
回调中引用函数名称。
return def.resolve()
returns jQuery.Deferred()
对象,而不是 jQuery promise 对象;尝试在 .resolve()
之后添加 .promise()
到 return jQuery promise object .
jQuery.ajax()
returns jQuery 承诺对象;不需要创建新的 jQuery $.Deferred()
对象,可以 return $.ajax()
initialize: function () {
console.log('AppView::initialized!');
var _this = this;
$.when( _this.processCookies() )
// reference function name, not invoked
.then( _this.loadAdScripts )
.then( _this.createChildViews );
},
processCookies: function () {
// no asynchronous operations appear here,
// no need to include `$.Deferred()` or `.promise()` object
// var def = $.Deferred();
console.log('(1) PROCESS COOKIES');
// return jQuery promise object, not deferred object
// return def.resolve().promise();
},
/**
* Instantiates new instances of the child views.
*/
createChildViews: function () {
var _this = this;
console.log('(4) CREATING CHILD VIEWS');
},
loadAdScripts: function () {
//var _this = this,
// def = $.Deferred();
return $.when(
_this.insertScript({
name: 'example1',
async: false,
src: '//www.example.com/script1.js',
}),
_this.insertScript({
is_mobile: is_mobile,
name: 'example2',
async: true,
src: '//example.com/script2.js'
})
)
.done(function () {
console.log('(3) ALL SCRIPTS LOADED');
// def.resolve();
});
},
insertScript: function (script) {
// var def = $.Deferred(),
protocol = (document.location.protocol === 'https:' ? 'https:' : 'http:');
// dont script 2 on mobile.
// if (script.name === 'example2' && script.is_mobile) {
// console.log('skipping script');
// return def.resolve().promise();
// }
var promise = script.name === 'example2' && script.is_mobile
? $.when()
: $.ajax({
dataType: 'script',
cache: false,
async: script.async,
url: protocol + script.src,
});
promise.done( function () {
console.log('(2) SINGLE SCRIPT LOADED');
// def.resolve();
});
},
EDIT: IMPORTANT NOTE this is using jQuery 1.7.2, and no it cannot be
changed from this version
编辑、更新
在不修改源的情况下,使用 jQuery 版本 1.7.2 可能无法实现预期顺序。
在 deferred.then ; see http://blog.jquery.com/2012/08/09/jquery-1-8-released/ , http://bugs.jquery.com/ticket/11010
更新后使用 jQuery 版本 1.8+ 时出现 return 预期顺序
1.8.0
var dfd = {
initialize: function () {
console.log('AppView::initialized!');
_this = dfd;
$.when(_this.processCookies())
// reference function name, not invoked
.then(_this.loadAdScripts)
.then(_this.createChildViews);
},
processCookies: function () {
// no asynchronous operations appear here,
// no need to include `$.Deferred()` or `.promise()` object
var def = $.Deferred(function (d) {
setTimeout(function () {
d.resolve('(1) PROCESS COOKIES')
}, Math.floor(Math.random() * 1000));
}).promise();
def.then(function (msg) {
console.log(msg);
});
return def.promise()
},
/**
* Instantiates new instances of the child views.
*/
createChildViews: function () {
_this = dfd;
console.log('(4) CREATING CHILD VIEWS');
},
loadAdScripts: function () {
_this = dfd;
return $.when.apply(_this, [_this.insertScript({
name: 'example1',
async: false,
src: '//www.example.com/script1.js',
}),
_this.insertScript({
is_mobile: true,
name: 'example2',
async: true,
src: '//example.com/script2.js'
})]).then(function () {
console.log('(3) ALL SCRIPTS LOADED');
})
},
insertScript: function (script) {
// var def = $.Deferred(),
protocol = (document.location.protocol === 'https:' ? 'https:' : 'http:');
// dont script 2 on mobile.
// if (script.name === 'example2' && script.is_mobile) {
// console.log('skipping script');
// return def.resolve();
// }
var promise = $.when( script.name === 'example2' && script.is_mobile ? $.Deferred(function (d) {
setTimeout(function () {
d.resolve('(2) skipping script', protocol + script.src)
}, Math.floor(Math.random() * 1000))
}).promise() : $.Deferred(function (d) {
setTimeout(function () {
d.resolve('(2) SINGLE SCRIPT LOADED', protocol + script.src)
}, Math.floor(Math.random() * 1000))
}).promise())
return promise.then(function(msg) {
console.log(msg)
});
}
};
dfd.initialize();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.0/jquery.min.js"></script>
1.72
var dfd = {
initialize: function () {
console.log('AppView::initialized!');
_this = dfd;
$.when(_this.processCookies())
// reference function name, not invoked
.then(_this.loadAdScripts)
.then(_this.createChildViews);
},
processCookies: function () {
// no asynchronous operations appear here,
// no need to include `$.Deferred()` or `.promise()` object
var def = $.Deferred(function (d) {
setTimeout(function () {
d.resolve('(1) PROCESS COOKIES')
}, Math.floor(Math.random() * 1000));
}).promise();
def.then(function (msg) {
console.log(msg);
});
return def.promise()
},
/**
* Instantiates new instances of the child views.
*/
createChildViews: function () {
_this = dfd;
console.log('(4) CREATING CHILD VIEWS');
},
loadAdScripts: function () {
_this = dfd;
return $.when.apply(_this, [_this.insertScript({
name: 'example1',
async: false,
src: '//www.example.com/script1.js',
}),
_this.insertScript({
is_mobile: true,
name: 'example2',
async: true,
src: '//example.com/script2.js'
})]).then(function () {
console.log('(3) ALL SCRIPTS LOADED');
})
},
insertScript: function (script) {
// var def = $.Deferred(),
protocol = (document.location.protocol === 'https:' ? 'https:' : 'http:');
// dont script 2 on mobile.
// if (script.name === 'example2' && script.is_mobile) {
// console.log('skipping script');
// return def.resolve();
// }
var promise = $.when( script.name === 'example2' && script.is_mobile ? $.Deferred(function (d) {
setTimeout(function () {
d.resolve('(2) skipping script', protocol + script.src)
}, Math.floor(Math.random() * 1000))
}).promise() : $.Deferred(function (d) {
setTimeout(function () {
d.resolve('(2) SINGLE SCRIPT LOADED', protocol + script.src)
}, Math.floor(Math.random() * 1000))
}).promise())
return promise.then(function(msg) {
console.log(msg)
});
}
};
dfd.initialize();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
编辑:重要说明这是使用 jQuery 1.7.2,不,不能从这个版本更改
我对 promises 不熟悉,并试图全神贯注于它们。我正在尝试按顺序执行一系列功能,等待它们完成后再创建一些子视图(在 Backbone.js 中)。这是我的代码:
initialize: function () {
console.log('AppView::initialized!');
var _this = this;
$.when( _this.processCookies() )
.then( _this.loadAdScripts() )
.then( _this.createChildViews() );
},
processCookies: function () {
var def = $.Deferred();
console.log('(1) PROCESS COOKIES');
return def.resolve();
},
/**
* Instantiates new instances of the child views.
*/
createChildViews: function () {
var _this = this;
console.log('(4) CREATING CHILD VIEWS');
},
loadAdScripts: function () {
var _this = this,
def = $.Deferred();
$.when(
_this.insertScript({
name: 'example1',
async: false,
src: '//www.example.com/script1.js',
}),
_this.insertScript({
is_mobile: is_mobile,
name: 'example2',
async: true,
src: '//example.com/script2.js'
})
)
.done(function () {
console.log('(3) ALL SCRIPTS LOADED');
def.resolve();
});
},
insertScript: function (script) {
var def = $.Deferred(),
protocol = (document.location.protocol === 'https:' ? 'https:' : 'http:');
// dont script 2 on mobile.
if (script.name === 'example2' && script.is_mobile) {
console.log('skipping script');
return def.resolve();
}
var promise = $.ajax({
dataType: 'script',
cache: false,
async: script.async,
url: protocol + script.src,
});
promise.done( function () {
console.log('(2) SINGLE SCRIPT LOADED');
return def.resolve();
});
},
因此,此处所需的流程是:
- 当
processCookies()
函数完成后, - 执行
loadAdScripts
函数 2a.insertScript()
触发,脚本 1 加载 2b.insertScript()
触发,脚本 2 加载 - 两个脚本都完成后,执行
createChildViews
函数。
所以,观察代码中的 console.log()
占位符,我 期望 在我的控制台中看到:
'(1) PROCESS COOKIES'
'(2) SINGLE SCRIPT LOADED'
'(2) SINGLE SCRIPT LOADED'
'(3) ALL SCRIPTS LOADED'
'(4) CREATING CHILD VIEWS'
然而我实际上看到的是:
'(1) PROCESS COOKIES'
'(3) ALL SCRIPTS LAODED'
'(4) CREATING CHILD VIEWS'
'(2) SINGLE SCRIPT LOADED'
'(2) SINGLE SCRIPT LOADED'
我的承诺有什么问题,为什么它们没有按预期的顺序执行?
$.when( _this.processCookies() )
.then( _this.loadAdScripts() )
.then( _this.createChildViews() );
似乎正在立即调用 loadScripts()
、 createChildViews()
,而是尝试在 .then(_this.loadAdScripts)
回调中引用函数名称。
return def.resolve()
returns jQuery.Deferred()
对象,而不是 jQuery promise 对象;尝试在 .resolve()
之后添加 .promise()
到 return jQuery promise object .
jQuery.ajax()
returns jQuery 承诺对象;不需要创建新的 jQuery $.Deferred()
对象,可以 return $.ajax()
initialize: function () {
console.log('AppView::initialized!');
var _this = this;
$.when( _this.processCookies() )
// reference function name, not invoked
.then( _this.loadAdScripts )
.then( _this.createChildViews );
},
processCookies: function () {
// no asynchronous operations appear here,
// no need to include `$.Deferred()` or `.promise()` object
// var def = $.Deferred();
console.log('(1) PROCESS COOKIES');
// return jQuery promise object, not deferred object
// return def.resolve().promise();
},
/**
* Instantiates new instances of the child views.
*/
createChildViews: function () {
var _this = this;
console.log('(4) CREATING CHILD VIEWS');
},
loadAdScripts: function () {
//var _this = this,
// def = $.Deferred();
return $.when(
_this.insertScript({
name: 'example1',
async: false,
src: '//www.example.com/script1.js',
}),
_this.insertScript({
is_mobile: is_mobile,
name: 'example2',
async: true,
src: '//example.com/script2.js'
})
)
.done(function () {
console.log('(3) ALL SCRIPTS LOADED');
// def.resolve();
});
},
insertScript: function (script) {
// var def = $.Deferred(),
protocol = (document.location.protocol === 'https:' ? 'https:' : 'http:');
// dont script 2 on mobile.
// if (script.name === 'example2' && script.is_mobile) {
// console.log('skipping script');
// return def.resolve().promise();
// }
var promise = script.name === 'example2' && script.is_mobile
? $.when()
: $.ajax({
dataType: 'script',
cache: false,
async: script.async,
url: protocol + script.src,
});
promise.done( function () {
console.log('(2) SINGLE SCRIPT LOADED');
// def.resolve();
});
},
EDIT: IMPORTANT NOTE this is using jQuery 1.7.2, and no it cannot be changed from this version
编辑、更新
在不修改源的情况下,使用 jQuery 版本 1.7.2 可能无法实现预期顺序。
在 deferred.then ; see http://blog.jquery.com/2012/08/09/jquery-1-8-released/ , http://bugs.jquery.com/ticket/11010
更新后使用 jQuery 版本 1.8+ 时出现 return 预期顺序1.8.0
var dfd = {
initialize: function () {
console.log('AppView::initialized!');
_this = dfd;
$.when(_this.processCookies())
// reference function name, not invoked
.then(_this.loadAdScripts)
.then(_this.createChildViews);
},
processCookies: function () {
// no asynchronous operations appear here,
// no need to include `$.Deferred()` or `.promise()` object
var def = $.Deferred(function (d) {
setTimeout(function () {
d.resolve('(1) PROCESS COOKIES')
}, Math.floor(Math.random() * 1000));
}).promise();
def.then(function (msg) {
console.log(msg);
});
return def.promise()
},
/**
* Instantiates new instances of the child views.
*/
createChildViews: function () {
_this = dfd;
console.log('(4) CREATING CHILD VIEWS');
},
loadAdScripts: function () {
_this = dfd;
return $.when.apply(_this, [_this.insertScript({
name: 'example1',
async: false,
src: '//www.example.com/script1.js',
}),
_this.insertScript({
is_mobile: true,
name: 'example2',
async: true,
src: '//example.com/script2.js'
})]).then(function () {
console.log('(3) ALL SCRIPTS LOADED');
})
},
insertScript: function (script) {
// var def = $.Deferred(),
protocol = (document.location.protocol === 'https:' ? 'https:' : 'http:');
// dont script 2 on mobile.
// if (script.name === 'example2' && script.is_mobile) {
// console.log('skipping script');
// return def.resolve();
// }
var promise = $.when( script.name === 'example2' && script.is_mobile ? $.Deferred(function (d) {
setTimeout(function () {
d.resolve('(2) skipping script', protocol + script.src)
}, Math.floor(Math.random() * 1000))
}).promise() : $.Deferred(function (d) {
setTimeout(function () {
d.resolve('(2) SINGLE SCRIPT LOADED', protocol + script.src)
}, Math.floor(Math.random() * 1000))
}).promise())
return promise.then(function(msg) {
console.log(msg)
});
}
};
dfd.initialize();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.0/jquery.min.js"></script>
1.72
var dfd = {
initialize: function () {
console.log('AppView::initialized!');
_this = dfd;
$.when(_this.processCookies())
// reference function name, not invoked
.then(_this.loadAdScripts)
.then(_this.createChildViews);
},
processCookies: function () {
// no asynchronous operations appear here,
// no need to include `$.Deferred()` or `.promise()` object
var def = $.Deferred(function (d) {
setTimeout(function () {
d.resolve('(1) PROCESS COOKIES')
}, Math.floor(Math.random() * 1000));
}).promise();
def.then(function (msg) {
console.log(msg);
});
return def.promise()
},
/**
* Instantiates new instances of the child views.
*/
createChildViews: function () {
_this = dfd;
console.log('(4) CREATING CHILD VIEWS');
},
loadAdScripts: function () {
_this = dfd;
return $.when.apply(_this, [_this.insertScript({
name: 'example1',
async: false,
src: '//www.example.com/script1.js',
}),
_this.insertScript({
is_mobile: true,
name: 'example2',
async: true,
src: '//example.com/script2.js'
})]).then(function () {
console.log('(3) ALL SCRIPTS LOADED');
})
},
insertScript: function (script) {
// var def = $.Deferred(),
protocol = (document.location.protocol === 'https:' ? 'https:' : 'http:');
// dont script 2 on mobile.
// if (script.name === 'example2' && script.is_mobile) {
// console.log('skipping script');
// return def.resolve();
// }
var promise = $.when( script.name === 'example2' && script.is_mobile ? $.Deferred(function (d) {
setTimeout(function () {
d.resolve('(2) skipping script', protocol + script.src)
}, Math.floor(Math.random() * 1000))
}).promise() : $.Deferred(function (d) {
setTimeout(function () {
d.resolve('(2) SINGLE SCRIPT LOADED', protocol + script.src)
}, Math.floor(Math.random() * 1000))
}).promise())
return promise.then(function(msg) {
console.log(msg)
});
}
};
dfd.initialize();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>