首先,阅读一下这两篇文章。
通过上面两篇文章简单了解了Promise后,再来思考一下
why promise?
假设我们有一个需求,执行一个ajax请求,当请求成功后,再次执行一个ajax请求,代码实现可以这样写(以jquery为例)
$.get(url1,function(data){
console.log('第一个ajax执行成功')
$.get(url2,function(data){
console.log('第二个ajax执行成功')
})
})
复制代码
当我们需要在回调里执行第二次异步操作时,可以这样写,那如果我们的需求是需要连续十次执行,每次异步操作的执行都是在上一个异步操作的回调中执行,难道还要层层嵌套的写吗?当然不是,这样写的话会让代码非常不优雅,并且耦合度很大。 到这里,就轮到今天的主角-Promise登场了! 参考阮一峰大神的文章,可以看到Promise的大概实现如下
fn().then(fn1).then(fn2) //fn执行完后执行fn1,fn1执行完后执行fn2
复制代码
把上面提到的需求带进来,那就可以使用链式操作来完成我们的需求,可以看出,promise能够将异步的代码用同步的方式表示出来
Promise的简单示例我们可以参考MDN的简单示例
var myFirstPromise = new Promise(function(resolve, reject){
//当异步代码执行成功时,我们才会调用resolve(...), 当异步代码失败时就会调用reject(...)
//在本例中,我们使用setTimeout(...)来模拟异步代码,实际编码时可能是XHR请求或是HTML5的一些API方法.
setTimeout(function(){
resolve("成功!"); //代码正常执行!
}, 250);
});
myFirstPromise.then(function(successMessage){
//successMessage的值是上面调用resolve(...)方法传入的值.
//successMessage参数不一定非要是字符串类型,这里只是举个例子
console.log("Yay! " + successMessage);
});
复制代码
接下来,我们自己动手写一个简单的Promise来对其原理有一个初步的认识。
何从下手?从使用开始
编写代码之前,先写一个简单的promise的执行语句
fn().then(fn1).then(fn2)
复制代码
参考官方文档,我们知道,then是promise对象的方法,这句代码能够链式调用,就说明fn最后返回的是一个promise对象,有头绪了!继续完善我们的代码
var p = new Promise() //创建一个promise对象,作为fn的返回
function fn() {
console.log(1)
setTimeout(function(){
p.resolve("成功!") //代码正常执行!这里执行异步回调
},1000)
return p
}
fn().then(fn1).then(fn2)
复制代码
then方法包含两个参数:onfulfilled 和 onrejected,它们都是 Function 类型。当Promise状态为fulfilled时,调用 then 的 onfulfilled 方法,当Promise状态为rejected时,调用 then 的 onrejected 方法,这里我们假设每次异步都是执行成功的,暂时只传fulfilled一个函数作为参数,接下来,完善Promise对象,这里我们把Promise对象的onfulfilled 方法的属性名设为resolve,onrejected 方法的属性名设为reject,完善Promise
function Promise(){
}
Promise.prototype = {
then: function(onfulfilled, onrejected){
//then方法包含的两个方法参数
this.resolve = onfulfilled
this.reject = onrejected
return this //then也可以链式调用,所以执行完成后返回promise实例对象
},
resolve: function(result){
// 回调成功执行的方法
},
reject: function(result){
// 回调失败执行的方法
}
}
var p = new Promise() //创建一个promise对象,作为fn的返回
function fn() {
console.log(1)
setTimeout(function(){
p.resolve("成功!") //代码正常执行!这里执行异步回调
},1000)
return p
}
fn().then(fn1).then(fn2)
复制代码
等等,貌似哪里不对?当我们语句中包含了两个then语句后,那就需要两个function放到任务队列中等待执行啊,如果这样写,后面的then的function岂不是会覆盖前面一个then的function吗?思考一下,我们可以把待执行的函数放在数组里顺序执行呀!继续修改代码
function Promise(){
this.callbacks = []
}
Promise.prototype = {
then: function(onfulfilled, onrejected){
this.callbacks.push({
resolve: onfulfilled,
reject: onrejected
})
return this //then也可以链式调用,所以执行完成后返回promise实例对象
},
resolve: function(result){
// 回调成功执行的方法
var callbackObj = this.callbacks.shift()
callbackObj['resolve'](result)
},
reject: function(result){
// 回调失败执行的方法
var callbackObj = this.callbacks.shift()
callbackObj['reject'](result)
}
}
var p = new Promise()
function fn() {
console.log(1)
setTimeout(function(){
p.resolve("成功!") //代码正常执行!这里执行异步回调
},1000)
return p
}
fn().then(fn1).then(fn2)
复制代码
修改完成,看看还有哪里可以优化的地方?咦?resolve和reject的函数执行长得好像啊,是不是可以提取出公共部分来执行呢?继续修改,顺便完善一下fn1和fn2函数
function Promise(){
this.callbacks = []
}
Promise.prototype = {
then: function(onfulfilled, onrejected){
this.callbacks.push({
resolve: onfulfilled,
reject: onrejected
})
return this //then也可以链式调用,所以执行完成后返回promise实例对象
},
resolve: function(result){
// 回调成功执行的方法
this.complete('resolve', result)
},
reject: function(result){
// 回调失败执行的方法
this.complete('reject', result)
},
complete: function(type, result){
var callbackObj = this.callbacks.shift()
callbackObj[type](result)
}
}
var p = new Promise()
function fn() {
console.log('我是立即执行语句~')
setTimeout(function(){
p.resolve("成功!") //代码正常执行!这里执行异步回调
},1000)
return p
}
function fn1(result) {
console.log('fn1', result)
setTimeout(function() {
p.resolve('data2')
}, 2000)
}
function fn2(result) {
console.log('fn2', result)
}
fn().then(fn1).then(fn2)
复制代码
初步完成,把代码放到html中执行,效果如下
是不是还差了点儿什么?
再回头看看MDN文档,我们发现Promise的原型上有两个方法,then和catch,then我们已经实现了,接下来再实现一个简单的catch。回到我们的代码,当我们执行fn失败时,可以通过fn().then(successFunction,errorFunction)的形式来执行错误处理函数errorFunction,但是到这一步已经出错了,正常的逻辑是应该直接结束后续的回调,跳出这个流程,而我们目前完成的代码貌似还是会继续往后执行。。。再改改!
function Promise(){
this.callbacks = []
this.oncatch = null //定义一个oncatch属性获取错误处理方法
}
Promise.prototype = {
then: function(onfulfilled, onrejected){
this.callbacks.push({
resolve: onfulfilled,
reject: onrejected
})
return this //then也可以链式调用,所以执行完成后返回promise实例对象
},
resolve: function(result){
// 回调成功执行的方法
this.complete('resolve', result)
},
reject: function(result){
// 回调失败执行的方法
this.complete('reject', result)
},
complete: function(type, result){
// 此处增加一个错误处理判断
if (type === 'reject' && this.oncatch) {
this.callbacks = []
this.oncatch(result)
} else if (this.callbacks[0]) {
var callbackObj = this.callbacks.shift()
if(callbackObj[type]) callbackObj[type](result)
}
},
catch: function(onfail){
this.oncatch = onfail
return this
}
}
var p = new Promise()
function fn() {
console.log('我是立即执行语句~')
setTimeout(function(){
p.reject("失败!") //代码正常执行!这里执行异步回调
},1000)
return p
}
function fn1(result) {
console.log('fn1', result)
setTimeout(function() {
p.resolve('data2')
}, 2000)
}
function fn2(result) {
console.log('fn2', result)
}
function errCatch(result) { //定义错误处理函数
console.log('err', result)
}
fn().then(fn1).then(fn2).catch(errCatch)
复制代码
至此,我们完成了一个简单的promise实现,来看看错误获取的效果
可以看到,fn1和fn2并没有执行!大功告成!
简单的Promise对象已经完成了,来实战操(zhuang)作(b)一波! 1.将Promise作为一个common.js规范模块来引用 Promise.js
function Promise() {
this.callbacks = []
this.oncatch = null
}
Promise.prototype = {
then: function(onfulfilled, onrejected) {
this.callbacks.push({
resolve: onfulfilled,
reject: onrejected
})
return this //then也可以链式调用,所以执行完成后返回promise实例对象
},
resolve: function(result) {
// 回调成功执行的方法
this.complete('resolve', result)
},
reject: function(result) {
// 回调失败执行的方法
this.complete('reject', result)
},
complete: function(type, result) {
if (type === 'reject' && this.oncatch) {
this.callbacks = []
this.oncatch(result)
} else if (this.callbacks[0]) {
var callbackObj = this.callbacks.shift()
if (callbackObj[type]) callbackObj[type](result)
}
},
catch: function(onfail) {
this.oncatch = onfail
return this
}
}
module.exports = Promise
复制代码
2.新建3个txt文件,内容随意,再新建一个test.js文件
var Promise = require('./Promise')
var fs = require('fs')
var p = new Promise
var str = ''
function readFile(path){
fs.readFile(path, 'utf-8', function(err, str){
if(err){
p.reject(path)
}else{
p.resolve(str)
}
})
return p
}
readFile('a.txt').then(function(line){
str += line
console.log('读取a...')
readFile('b.txt')
}).then(function(line){
str += line
console.log('读取b...')
readFile('c.txt')
}).then(function(line){
str += line
console.log('读取c...')
p.resolve(str)
}).then(function(result){
console.log(result)
}).catch(function(){
console.log('err')
})
复制代码
目录结构如下
在node环境下运行test.js,效果如下
再把某个读取步骤的路径改为一个不存在的文件试试
至此,一个简单的promise实现完成,如有错误,欢迎指正!拜拜