我们先了解学会promise用法,为手写promise做准备。[握拳小手]
参考文章:
廖雪峰的官网
使用 Promise
Promise 对象用于表示一个异步操作的最终完成 (或失败), 及其结果值,是一种承诺将来会执行”的对象。
前言
在JavaScript的世界中,所有代码都是单线程执行的。
由于这个“缺陷”,导致JavaScript的所有网络操作,浏览器事件,都必须是异步执行。异步执行可以用回调函数实现:
function callback() {
console.log("Done。。");
}
console.log("before setTimeout()..");
setTimeout(callback, 1000);
console.log("after setTimeout()....");
你会在chrome控制台输出看到:
before setTimeout()
after setTimeout()
(等待1秒后)
Done
可见,异步操作会在将来的某个时间点触发一个函数调用。
AJAX就是典型的异步操作。以代码为例:
request.onreadystatechange = function () {
if (request.readyState === 4) {
if (request.status === 200) {
return success(request.responseText);
} else {
return fail(request.status);
}
}
}
把回调函数success(request.responseText)和fail(request.status)写到一个AJAX操作里很正常,但是不好看,而且不利于代码复用。
有没有更好的写法?比如写成这样:
var ajax = ajaxGet('http://...');
ajax.ifSuccess(success)
.ifFail(fail);
这种链式写法的好处在于,先统一执行AJAX逻辑,不关心如何处理结果,然后,根据结果是成功还是失败,在将来的某个时候调用success函数或fail函数。
测试一下你的浏览器是否支持Promise:
'use strict';
// 直接运行测试:
new Promise(function () {});
简单例子
我们先看一个最简单的Promise例子:生成一个0-2之间的随机数,如果小于1,则等待一段时间后返回成功,否则返回失败:
function test(resolve, reject) {
var timeOut = Math.random() *2;
log('set timeout to: ' + timeOut + ' seconds.');
setTimeout(function() {
if(timeOut <1) {
log('call resolve()...');
resolve('200 OK');
}
else {
log('call reject()...');
reject('timeout in ' + timeOut + ' seconds.');
}
},timeOut*1000)
}
这个test()函数有两个参数,这两个参数都是函数,如果执行成功,我们将调用resolve('200 OK'),如果执行失败,我们将调用reject('timeout in ' + timeOut + ' seconds.')。可以看出,test()函数只关心自身的逻辑,并不关心具体的resolve和reject将如何处理结果。
有了执行函数,我们就可以用一个Promise对象来执行它,并在将来某个时刻获得成功或失败的结果:
var p1 = new promise(test);
var p2 = p1.then(function(result) {
console.log("成功"+ result);
});
var p3 = p2.catch(function(reson) {
console.log("失败"+reson);
});
变量p1是一个Promise对象,它负责执行test函数。由于test函数在内部是异步执行的,当test函数执行成功时,我们告诉Promise对象:
// 如果成功,执行这个函数:
p1.then(function (result) {
console.log('成功:' + result);
});
当test函数执行失败时,我们告诉Promise对象:
p2.catch(function (reason) {
console.log('失败:' + reason);
});
Promise对象可以串联起来,所以上述代码可以简化为:
new promise(test).then(function(tesult) {
console.log("成功"+result);
}).catch(function(reason) {
console.log("失败"+reason);
})
实际测试一下,看看Promise是如何异步执行的:
'use strict';
// 清除log:
var logging = document.getElementById('test-promise-log');
while (logging.children.length > 1) {
logging.removeChild(logging.children[logging.children.length - 1]);
}
// 输出log到页面:
function log(s) {
var p = document.createElement('p');
p.innerHTML = s;
logging.appendChild(p);
};
new Promise(function (resolve, reject) {
log('start new Promise...');
var timeOut = Math.random() * 2;
log('set timeout to: ' + timeOut + ' seconds.');
setTimeout(function () {
if (timeOut < 1) {
log('call resolve()...');
resolve('200 OK');
}
else {
log('call reject()...');
reject('timeout in ' + timeOut + ' seconds.');
}
}, timeOut * 1000);
}).then(function (r) {
log('Done: ' + r);
}).catch(function (reason) {
log('Failed: ' + reason);
});
可见Promise最大的好处是在异步执行的流程中,把执行代码和处理结果的代码清晰地分离了:
链式调用
连续执行两个或者多个异步操作是一个常见的需求,在上一个操作执行成功之后,开始下一个的操作,并带着上一步操作所返回的结果。我们可以通过创造一个 Promise 链来实现这种需求。
见证奇迹的时刻:then() 函数会返回一个和原来不同的新的Promise。
job1.then(job2).then(job3).catch(handleError);
其中,job1、job2和job3都是Promise对象。
基本上,每一个 Promise 都代表了链中另一个异步过程的完成。
在过去,要想做多重的异步操作,会导致经典的回调地狱:
doSomething(function(result) {
doSomethingElse(result, function(newResult) {
doThirdThing(newResult, function(finalResult) {
console.log('Got the final result: ' + finalResult);
}, failureCallback);
}, failureCallback);
}, failureCallback);
现在,我们可以把回调绑定到返回的 Promise 上,形成一个 Promise 链:
doSomething().then(function(result) {
return doSomethingELse(result);
}).then(function(newResult) {
return doThirdThing(newResult);
}).then(function(finalResult) {
console.log('Got the final result: ' + finalResult);
})
.catch(failureCallback);
//等价于.then(null, failureCallback);
注意:
- 定要有返回值,否则,callback 将无法获取上一个 Promise 的结果。
- (如果使用箭头函数,() => x 比 () => { return x; } 更简洁一些,但后一种保留 return 的写法才支持使用多个语句。)。
下面的例子演示了如何串行执行一系列需要异步计算获得结果的任务:
var p = new Promise(function (resolve, reject) {
log('start new Promise...');
resolve(2);
});
p.then(multiply)
.then(add)
.then(multiply)
.then(add)
.then(function (result) {
consle.log('Got value: ' + result);
});
// 0.5秒后返回input*input的计算结果:
function multiply(input) {
return new Promise(function(resolve, reject) {
console.log("calculating "+ input+' x '+input+ "....");
setTimeout(resolve, 500, input*input) ;
})
}
//0.5秒后返回input+input的计算结果:
function add(input) {
return new Promise(function(resolve, reject) {
console.log("calculating "+ input+' +'+input+ "....");
setTimeout(resolve, 500, input+input);
})
}
Promise.all()
除了串行执行若干异步任务外,Promise还可以并行执行异步任务。
试想一个页面聊天系统,我们需要从两个不同的URL分别获得用户的个人信息和好友列表,这两个任务是可以并行执行的,用Promise.all()实现如下:
var p1 = new Promise(function (resolve, reject) {
setTimeout(resolve, 500, 'P1');
});
var p2 = new Promise(function (resolve, reject) {
setTimeout(resolve, 600, 'P2');
});
// 同时执行p1和p2,并在它们都完成后执行then:
Promise.all([p1, p2]).then(function (results) {
// 获得一个Array: ['P1', 'P2']
console.log(results);
});
Promise.race()
多个异步任务是为了容错。比如,同时向两个URL读取用户的个人信息,只需要获得先返回的结果即可。这种情况下,用Promise.race()实现:
var p1 = new Promise(function (resolve, reject) {
setTimeout(resolve, 500, 'P1');
});
var p2 = new Promise(function (resolve, reject) {
setTimeout(resolve, 600, 'P2');
});
Promise.race([p1, p2]).then(function (result) {
console.log(result); // 'P1'
});
由于p1执行较快,Promise的then()将获得结果'P1'。p2仍在继续执行,但执行结果将被丢弃。
如果我们组合使用Promise,就可以把很多异步任务以并行和串行的方式组合起来执行。
常见的错误
在编写 Promise 链时,需要注意以下示例中展示的几个错误:
doSomething().then(function(result) {
doSomethingElse(result);
// 没有返回 Promise 以及没有必要的嵌套 Promise
}).then(newResult => doThirdThing(newResult));
.then(() => doFourthThing());
// 最后,是没有使用 catch 终止 Promise 调用链,可能导致没有捕获的异常
1.错误是没有正确地将事物相连接。当我们创建新 Promise 但忘记返回它时,会发生这种情况。(同时在执行两个异步而非一个一个的执行)。这意味着 doFourthThing() 不会等待 doSomethingElse() 或 doThirdThing() 完成.
2.不必要地嵌套,实现第一个错误。嵌套还限制了内部错误处理程序的范围,如果是非预期的,可能会导致未捕获的错误。
3.忘记用 catch 终止链。这导致在大多数浏览器中不能终止的 Promise 链里的 rejection。
一个好的经验法则是总是返回或终止 Promise 链,并且一旦你得到一个新的 Promise,返回它。下面是修改后的平面化的代码:
doSomething().then(function(result) {
return doSomethingElse(result);
}).then(function(newResult) {
return doThirdThing(newResult);
}).then(function(thirResult) {
console.log("third"+thirResult);
}).actch(function(error) {
console.log(error)
});
//或者
doSomething().then(result=>{ return doSomethingElse(result)})
.then(newResult=> {return doThirdThing(newResult)})
.then(()=> {return doFourthThing()})
.catch(error => console.log(error));
上述代码的写法就是具有适当错误处理的简单明确的链式写法。
使用 async/await 可以解决以上大多数错误.
理解了promise 的用法 下一节我们来手写实现一个promise。加油~