学习Promise笔记
什么是Promise?
MDN对Promise的定义:Promise对象用于异步操作,它表示一个尚未完成且预计在未来完成的异步操作。
在学习Promise之前得先了解同步与异步:
JavaScript的执行环境是单线程。所谓单线程,是指JS引擎中负责解释和执行JavaScript代码的线程只有一个,也就是一次只能完成一项任务,这个任务执行完后才能执行下一个,它会阻塞其他任务。这个任务可称为主线程。
但实际上还有其他线程,如事件触发线程,Ajax请求线程等。
同步:
同步模式,即上述所说的单线程模式,一次只能执行一个任务,函数调用后需要等到函数执行结束,返回执行结果,才能进行下一个任务。如果这个任务执行的时间较长,就会导致线程阻塞。
var x = true;
while(x);
console.log("don't carry out"); // 不会执行
上面代码中的while是一个死循环,它会阻塞进程,因此第三句console不会执行。
异步:
异步模式,即与同步模式相反,可以一起执行多个任务,函数调用后不会立即执行返回执行的结果,如果任务A需要等待,可先执行任务B,等到任务A结果返回后继续回调。
最常见的异步模式就是定时器的使用:
setTimeout(function() {
console.log('taskA, asynchronous');
}, 0);
console.log('taskB, synchronize');
//while(true); -------ouput-------
taskB, synchronize
taskA, asynchronous
虽然定时器延时的时间为0,但taskA还是晚于taskB执行。这是因为定时器是异步的,异步任务会再当前脚本的所有同步任务执行完后才会执行。如果同步代码中含有死循环,则这个异步任务不会执行,因为同步任务阻塞了进程。
回调函数:
上例中,setTimeout里的function便是回调函数。可以理解为:(执行完)回调的函数。
WikiPedia对callback的定义可以理解为:回调函数是一段可执行的代码段,它以参数的形式传递给其他代码,在其合适的时间执行这段(回调函数)的代码。回调函数不仅是可以用于异步调用,一般同步的场景也可以用回调。在同步调用下,可能一段时间后执行执行或不执行(未达到执行的条件)
/******************同步回调******************/
var fun1 = function(callback) {
//do something
console.log("before callback");
(callback && typeof(callback) === 'function') && callback();
console.log("after callback");
}
var fun2 = function(param) {
//do something
var start = new Date();
while((new Date() - start) < 3000) { //delay 3s
}
console.log("I'm callback");
}
fun1(fun2); -------output--------
before callback
//after 3s
I’m callback
after callback
由于是同步调用,会阻塞后面的代码,如果fun2是个死循环,后面的代码就不执行了。
除了上面setTimeout为常见的异步回调,另外常见的异步即Ajax请求:
/******************异步回调******************/
function request(url, param, successFun, errorFun) {
$.ajax({
type: 'GET',
url: url,
param: param,
async: true, //默认为true,即异步请求;false为同步请求
success: successFun,
error: errorFun
});
}
request('test.html', '', function(data) {
//请求成功后的回调函数,通常是对请求回来的数据进行处理
console.log('请求成功啦, 这是返回的数据:', data);
},function(error) {
console.log('sorry, 请求失败了, 这是失败信息:', error);
});
为什么使用Promise
利用Promise改写上面Ajax的例子:
function sendRequest(url, param) {
return new Promise(function (resolve, reject) {
request(url, param, resolve, reject);
});
} sendRequest('test.html', '').then(function(data) {
//异步操作成功后的回调
console.log('请求成功啦, 这是返回的数据:', data);
}, function(error) {
//异步操作失败后的回调
console.log('sorry, 请求失败了, 这是失败信息:', error);
});
Promise的优势在于它的重链式调用,可以避免层层嵌套回调。如果第一次Ajax请求后,还可以用它的返回的结果再次请求
request('test1.html', '', function(data1) {
console.log('第一次请求成功, 这是返回的数据:', data1);
request('test2.html', data1, function (data2) {
console.log('第二次请求成功, 这是返回的数据:', data2);
request('test3.html', data2, function (data3) {
console.log('第三次请求成功, 这是返回的数据:', data3);
//request... 继续请求
}, function(error3) {
console.log('第三次请求失败, 这是失败信息:', error3);
});
}, function(error2) {
console.log('第二次请求失败, 这是失败信息:', error2);
});
}, function(error1) {
console.log('第一次请求失败, 这是失败信息:', error1);
});
以上出现了多层调用,难以明白层级之间的关系,这就是常说的回调地狱(Pyramid of Doom),而使用Promise,可以利用then进行链式调用,将异步操作以同步操作的流程表示出来。
sendRequest('test1.html', '').then(function(data1) {
console.log('第一次请求成功, 这是返回的数据:', data1);
}).then(function(data2) {
console.log('第二次请求成功, 这是返回的数据:', data2);
}).then(function(data3) {
console.log('第三次请求成功, 这是返回的数据:', data3);
}).catch(function(error) {
//用catch捕捉前面的错误
console.log('sorry, 请求失败了, 这是失败信息:', error);
});
Promise的基本用法
Promise对象代表一个未完成、但预计将来会完成的操作。它有以下三种状态:
pending:初始值,不是fulfilled,也不是rejected
fulfilled:代表操作成功
rejected:代表操作失败
Promise有两种状态改变的方式,既可以从pending转变为fulfilled,也可以从pending转变为rejected。一旦状态改变,就会一直保持这个状态。当状态发生变化,Promise.then绑定的函数就会被调用。
注意:Promise一旦新建就会立即执行,无法取消。这也是它的缺点之一。
下面通过一个例子进一步讲解:
//构建Promise
var promise = new Promise(function (resolve, reject) {
if (/* 异步操作成功 */) {
resolve(data);
} else {
/* 异步操作失败 */
reject(error);
}
});
类似构建对象,我们使用new来构建一个Promise。Promise接受一个函数作为参数,该函数的两个参数分别是resolve和reject。这两个函数就是回调函数,由JavaScript引擎提供。
resolve函数的作用:在异步操作成功时调用,并将异步操作的结果,作为参会素传递出去;
reject函数的作用:在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
Promise实例生成以后,可以用then方法指定resolve和reject状态的回调函数。
promise.then(onFulfilled, onRejected); promise.then(function(data) {
// do something when success
}, function(error) {
// do something when failure
});
then方法会返回一个Promise。它有两个参数,分别为Promise从pending变为fulfilled和rejected时的回调函数(第二个参数非必选)。这两个函数都接受Promise对象传出的值作为参数。
简单来说,then就是定义resolve和reject函数的,其resolve参数相当于:
function resolveFun(data) {
//data为promise传出的值
}
而新建的Promise中的‘resolve(data)’,则相当于执行resolveFun函数。
Promise新建后就会立即执行。而then方法中指定的回调函数,将在当前脚本所有同步任务执行完才会执行。如下例:
var promise = new Promise(function(resolve, reject) {
console.log('before resolved');
resolve();
console.log('after resolved');
}); promise.then(function() {
console.log('resolved');
}); console.log('outer'); -------output-------
before resolved
after resolved
outer
resolved
由于resolve指定的是异步操作成功后的回调函数,它需要等所有的同步代码执行后才会执行,因此最后打印‘resolved’
基本API
.then()
语法:Promise.prototype.then(onFulfilled,onRejected)
对Promise添加onFulfilled和onRejected回调,并返回的是一个新的Promise实例(不是原来那个Promise实例),且返回值将作为参数传入这个新Promise的resolve函数。
因此可以使用链式写法。由于前一个回调涵涵素,返回的还是一个Promise对象(即有异步操作),这时后一个回调函数,就会等待该Promise对象的状态发生变化,才会被调用。
.catch()
语法:Promise.prototype.catch(onRejected)
该方法是.then(undefined,onRejected)的别名,用于指定发生错误时的回调函数。
promise.then(function(data) {
console.log('success');
}).catch(function(error) {
console.log('error', error);
}); /*******等同于*******/
promise.then(function(data) {
console.log('success');
}).then(undefined, function(error) {
console.log('error', error);
});
var promise = new Promise(function (resolve, reject) {
throw new Error('test');
});
/*******等同于*******/
var promise = new Promise(function (resolve, reject) {
reject(new Error('test'));
}); //用catch捕获
promise.catch(function (error) {
console.log(error);
});
-------output-------
Error: test
从上例可知,reject方法的作用等同于抛错。
promise对象的错误,会一直向后传递,直到被捕获。即错误总会被下一个catch所捕获。then方法指定的回调函数,若抛出错误,也会被下一个catch捕获。catch中也能抛错,则需要后面的catch来捕获。
sendRequest('test.html').then(function(data1) {
//do something
}).then(function (data2) {
//do something
}).catch(function (error) {
//处理前面三个Promise产生的错误
});
上面提到的,promise状态一旦改变就会凝固,不会再改变。因此promise一旦fulfilled了,再抛错,也不会变为rejected,就不会被catch了
var promise = new Promise(function(resolve, reject) {
resolve();
throw 'error';
}); promise.catch(function(e) {
console.log(e); //This is never called
});
var promise = new Promise(function(resolve, reject) {
resolve();
throw 'error';
}); promise.catch(function(e) {
console.log(e); //This is never called
});
如果没有使用catch方法指定处理错误的回调函数,Promise对象抛出的错误不会传递到外层代码,即不会有任何反应(Chrome会抛错),这是Promise的另一个缺点。
var promise = new Promise(function (resolve, reject) {
resolve(x);
});
promise.then(function (data) {
console.log(data);
});
如图所示,只有Chrome会抛错,且promise状态变为rejected,Firefox和Safari中错误不会被捕获,也不会传递到外层代码,最后没有任何输出,promise状态也变为rejected。
.all()
语法:Promise.all(iterable)
该方法用于将多个Promise实例,包装成一个新的Promise实例。
var p = Promise.all([p1, p2, p3]);
Promise.all方法接受一个数组(或具有Iterator接口)作参数,数组中的对象(p1,p2,p3)均为promise实例(如果不是一个promise,该项会被用Promise.resolve转换为一个promise)。它的状态由这三个promise实例来决定。
1.当p1,p2,p3状态都为fulfilled,p的状态才会变为fulfilled,并将三个promise返回的结果,按参数的顺序(而不是resolved的顺序)存入数组,传给p的回调函数;
/* 例3.8 */
var p1 = new Promise(function (resolve, reject) {
setTimeout(resolve, 3000, "first");
});
var p2 = new Promise(function (resolve, reject) {
resolve('second');
});
var p3 = new Promise((resolve, reject) => {
setTimeout(resolve, 1000, "third");
}); Promise.all([p1, p2, p3]).then(function(values) {
console.log(values);
}); -------output-------
//约 3s 后
["first", "second", "third"]
2.当p1,p2,p3其中之一状态变为rejected,p的状态也会变为rejected,并把第一个被rejected的promise的返回值,传给p的回调函数;
var p1 = new Promise((resolve, reject) => {
setTimeout(resolve, 1000, "one");
});
var p2 = new Promise((resolve, reject) => {
setTimeout(reject, 2000, "two");
});
var p3 = new Promise((resolve, reject) => {
reject("three");
}); Promise.all([p1, p2, p3]).then(function (value) {
console.log('resolve', value);
}, function (error) {
console.log('reject', error); // => reject three
}); -------output-------
reject three
这多个promise是同时开始、并行执行的,而不是顺序执行的。
function timerPromisefy(delay) {
return new Promise(function (resolve) {
setTimeout(function () {
resolve(delay);
}, delay);
});
}
var startDate = Date.now(); Promise.all([
timerPromisefy(1),
timerPromisefy(32),
timerPromisefy(64),
timerPromisefy(128)
]).then(function (values) {
console.log(Date.now() - startDate + 'ms');
console.log(values);
});
-------output-------
133ms //不一定,但大于128ms
[1,32,64,128]
.race()
语法:Promise.race(iterable)
该方法同样接受一个数组(或具有Iterator接口)作参数。当p1,p2,p3中有一个实例的状态发生改变(变为fulfilled或rejected),p的状态就跟着改变。并把第一个改变状态的promise的返回值,传给p的回调函数。
var p1 = new Promise(function(resolve, reject) {
setTimeout(reject, 500, "one");
});
var p2 = new Promise(function(resolve, reject) {
setTimeout(resolve, 100, "two");
}); Promise.race([p1, p2]).then(function(value) {
console.log('resolve', value);
}, function(error) {
//not called
console.log('reject', error);
});
-------output-------
resolve two var p3 = new Promise(function(resolve, reject) {
setTimeout(resolve, 500, "three");
});
var p4 = new Promise(function(resolve, reject) {
setTimeout(reject, 100, "four");
}); Promise.race([p3, p4]).then(function(value) {
//not called
console.log('resolve', value);
}, function(error) {
console.log('reject', error);
});
-------output-------
reject four
在第一个promise对象变为resolve后,并不会取消其他promise对象的执行,如下
var fastPromise = new Promise(function (resolve) {
setTimeout(function () {
console.log('fastPromise');
resolve('resolve fastPromise');
}, 100);
});
var slowPromise = new Promise(function (resolve) {
setTimeout(function () {
console.log('slowPromise');
resolve('resolve slowPromise');
}, 1000);
});
// 第一个promise变为resolve后程序停止
Promise.race([fastPromise, slowPromise]).then(function (value) {
console.log(value); // => resolve fastPromise
});
-------output-------
fastPromise
resolve fastPromise
slowPromise //仍会执行
.resolve()
语法:
Promise.resolve(value);
Promise.resolve(promise);
Promise.resolve(thenable);
它可以看做new Promise()的快捷方式
Promise.resolve('Success'); /*******等同于*******/
new Promise(function (resolve) {
resolve('Success');
});
这段代码会让这个Promise对象立即进入resolved状态,并将结果success传递给then指定的onFulfilled回调函数。由于Promise.resolve()也是返回Promise对象,因此可以用.then()处理其返回值。
Promise.resolve('success').then(function (value) {
console.log(value);
});
-------output-------
success
//Resolving an array
Promise.resolve([1,2,3]).then(function(value) {
console.log(value[0]); // => 1
}); //Resolving a Promise
var p1 = Promise.resolve('this is p1');
var p2 = Promise.resolve(p1);
p2.then(function (value) {
console.log(value); // => this is p1
});
Promise.resolve()的另一个作用就是将thenable对象(即带有then方向的对象)转换为promise对象。
var p1 = Promise.resolve({
then: function (resolve, reject) {
resolve("this is an thenable object!");
}
});
console.log(p1 instanceof Promise); // => true p1.then(function(value) {
console.log(value); // => this is an thenable object!
}, function(e) {
//not called
});
下面两个例子,无论是在什么时候抛异常,只要promise状态变成resolved或rejected,状态不会再改变,这和新建promise是一样的。
//在回调函数前抛异常
var p1 = {
then: function(resolve) {
throw new Error("error");
resolve("Resolved");
}
}; var p2 = Promise.resolve(p1);
p2.then(function(value) {
//not called
}, function(error) {
console.log(error); // => Error: error
}); //在回调函数后抛异常
var p3 = {
then: function(resolve) {
resolve("Resolved");
throw new Error("error");
}
}; var p4 = Promise.resolve(p3);
p4.then(function(value) {
console.log(value); // => Resolved
}, function(error) {
//not called
});
.reject()
语法:Promise.reject(reason)
这个方法和上述的Promise.resolve()类似,它也是new Promise()的快捷方式。
Promise.reject(new Error('error')); /*******等同于*******/
new Promise(function (resolve, reject) {
reject(new Error('error'));
});
这段代码会让这个Promise对象立即进入rejected状态,并将错误对象传递给then指定的onRejected回调函数。
Promise常见问题
总结一下创建promise的流程:
1.使用new Promise(fn)或者它的快捷方式Promise.resolve()、Promise.reject,返回一个promise对象
2.在fn中指定异步的处理
处理结果正常,调用resolve
处理结果错误,调用reject
如果使用ES6的箭头函数,将会使写法更加简单清晰
接下来用例子说明promise使用过程中的注意点及容易犯的错误。
情景1:reject和catch的区别
promise.then(onFulfilled,onRejected) 在onFulfilled中发生异常的话,在onRejected中是捕获不到这个异常的。
promise.then(onFilfilled).catch(onRejected) .then中产生的异常能在.catch中捕获
一般情况,使用第二种,第二种的.catch()也可以使用.then()表示,它们本质上没有区别,.catch===.then(null,onRejected)
情景2:如果在then中抛错,而没有对错进行处理(即catch),那么会一直保持reject状态,直到catch了错误
function taskA() {
console.log(x);
console.log("Task A");
}
function taskB() {
console.log("Task B");
}
function onRejected(error) {
console.log("Catch Error: A or B", error);
}
function finalTask() {
console.log("Final Task");
}
var promise = Promise.resolve();
promise
.then(taskA)
.then(taskB)
.catch(onRejected)
.then(finalTask); -------output-------
Catch Error: A or B,ReferenceError: x is not defined
Final Task
从代码的输出结果及流程,可以看出,A抛错时,会按照taskA->onRejected->finalTask这个流程来处理。A抛错后,若没有对它进行处理,状态就会维持rejected,taskB不会执行,直到catch了错误。
function taskA() {
console.log(x);
console.log("Task A");
}
function taskB() {
console.log("Task B");
}
function onRejectedA(error) {
console.log("Catch Error: A", error);
}
function onRejectedB(error) {
console.log("Catch Error: B", error);
}
function finalTask() {
console.log("Final Task");
}
var promise = Promise.resolve();
promise
.then(taskA)
.catch(onRejectedA)
.then(taskB)
.catch(onRejectedB)
.then(finalTask); -------output-------
Catch Error: A ReferenceError: x is not defined
Task B
Final Task
在TaskA后多了对A的处理,因此,A抛错时,会按照taskA->onRejectedA->taskB->finalTask这个流程来处理,此时taskB是正常执行的。
情景3:每次调用then都会返回一个新创建的promise对象,而then内部只是返回的数据
//方法1:对同一个promise对象同时调用 then 方法
var p1 = new Promise(function (resolve) {
resolve(100);
});
p1.then(function (value) {
return value * 2;
});
p1.then(function (value) {
return value * 2;
});
p1.then(function (value) {
console.log("finally: " + value);
});
-------output-------
finally: 100 //方法2:对 then 进行 promise chain 方式进行调用
var p2 = new Promise(function (resolve) {
resolve(100);
});
p2.then(function (value) {
return value * 2;
}).then(function (value) {
return value * 2;
}).then(function (value) {
console.log("finally: " + value);
});
-------output-------
finally: 400
第一中方法中,then的调用几乎是同时开始执行的,且传给每个then的value都是100,这种方法应当避免。方法二才是正确的链式调用。
容易出现下面的错误写法:
function badAsyncCall(data) {
var promise = Promise.resolve(data);
promise.then(function(value) {
//do something
return value + 1;
});
return promise;
}
badAsyncCall(10).then(function(value) {
console.log(value); //想要得到11,实际输出10
});
-------output-------
10
正确的写法:
function goodAsyncCall(data) {
var promise = Promise.resolve(data);
return promise.then(function(value) {
//do something
return value + 1;
});
}
goodAsyncCall(10).then(function(value) {
console.log(value);
});
-------output-------
11
情景4:在异步回调中抛错,不会被catch到
// Errors thrown inside asynchronous functions will act like uncaught errors
var promise = new Promise(function(resolve, reject) {
setTimeout(function() {
throw 'Uncaught Exception!';
}, 1000);
}); promise.catch(function(e) {
console.log(e); //This is never called
});
情景5:promise状态变为resolve或reject,就凝固了,不会再改变
console.log(1);
new Promise(function (resolve, reject){
reject();
setTimeout(function (){
resolve(); //not called
}, 0);
}).then(function(){
console.log(2);
}, function(){
console.log(3);
});
console.log(4); -------output-------
1
4
3
学习Promise笔记的更多相关文章
- 每天成长一点---WEB前端学习入门笔记
WEB前端学习入门笔记 从今天开始,本人就要学习WEB前端了. 经过老师的建议,说到他每天都会记录下来新的知识点,每天都是在围绕着这些问题来度过,很有必要每天抽出半个小时来写一个知识总结,及时对一天工 ...
- DSP28377S - ADC学习编程笔记
DSP28377S - ADC学习编程笔记 彭会锋 2016-08-04 20:19:52 1 ADC类型导致的配置区别 F28377S的ADC类型是Type 4类型,我的理解是不同类型的ADC采 ...
- 学习ReactNative笔记整理一___JavaScript基础
学习ReactNative笔记整理一___JavaScript基础 ★★★笔记时间- 2017-1-9 ★★★ 前言: 现在跨平台是一个趋势,这样可以减少开发和维护的成本.第一次看是看的ReactNa ...
- 深度学习word2vec笔记之算法篇
深度学习word2vec笔记之算法篇 声明: 本文转自推酷中的一篇博文http://www.tuicool.com/articles/fmuyamf,若有错误望海涵 前言 在看word2vec的资料 ...
- 强化学习读书笔记 - 02 - 多臂老O虎O机问题
# 强化学习读书笔记 - 02 - 多臂老O虎O机问题 学习笔记: [Reinforcement Learning: An Introduction, Richard S. Sutton and An ...
- 强化学习读书笔记 - 05 - 蒙特卡洛方法(Monte Carlo Methods)
强化学习读书笔记 - 05 - 蒙特卡洛方法(Monte Carlo Methods) 学习笔记: Reinforcement Learning: An Introduction, Richard S ...
- 强化学习读书笔记 - 06~07 - 时序差分学习(Temporal-Difference Learning)
强化学习读书笔记 - 06~07 - 时序差分学习(Temporal-Difference Learning) 学习笔记: Reinforcement Learning: An Introductio ...
- (转)深度学习word2vec笔记之基础篇
深度学习word2vec笔记之基础篇 声明: 1)该博文是多位博主以及多位文档资料的主人所无私奉献的论文资料整理的.具体引用的资料请看参考文献.具体的版本声明也参考原文献 2)本文仅供学术交流,非商用 ...
- 深度学习word2vec笔记之基础篇
作者为falao_beiliu. 作者:杨超链接:http://www.zhihu.com/question/21661274/answer/19331979来源:知乎著作权归作者所有.商业转载请联系 ...
随机推荐
- 资料收集:学习 Linux/*BSD/Unix 的 30 个最佳在线文档
文章转自:https://linux.cn/article-10311-1.html 手册页(man)是由系统管理员和 IT 技术开发人员写的,更多的是为了作为参考而不是教你如何使用.手册页对于已经熟 ...
- 白话SpringCloud | 第七章:分布式配置中心的使用
前言 介绍完服务的容错保护处理,接下来我们来了解下关于分布式配置中心的相关知识和使用.众所周知,随着项目的越来越多,日益庞大,每个子项目都会伴随着不同的配置项,于此也就多了很多的配置文件.倘若某些配置 ...
- 吴恩达《Machine Learning Yearning》总结(1-10章)
1.为什么选择机器学习策略 案例:建立猫咪图像识别app 系统的优化可以有很多的方向: (1)获取更多的数据集,即更多的图片: (2)收集更多多样数据,如处于不常见的位置的猫的图,颜色奇异的猫的照片等 ...
- [转]JS组件系列——BootstrapTable 行内编辑解决方案:x-editable
本文转自:http://www.cnblogs.com/landeanfen/p/5821192.html 阅读目录 一.x-editable组件介绍 二.bootstrapTable行内编辑初始方案 ...
- C# Winform小程序:局域网设置NTP服务器、实现时间同步
设置NTP服务器: NTP是网络时间协议(Network Time Protocol),它是用来同步网络中各个计算机的时间的协议. 局域网不能连接Internet,可以设置一台计算机为NTP服务器. ...
- 2015-08-19(i++与++i的思考)
今天看到一个很有趣的东西,关于表达式优先级的问题.如下. ; int j=(i++)+(i++); j的值是多少? 分析:编译器是从左往右编译也就是说编译器先做(i++) 1.(i++)由于是++所以 ...
- Java程序员应该知道的linux命令
1.查看Java进程:ps -ef|grep java,ps auxf|grep jva; 2.杀死所有Java进程: pkill java, kill -9 进程ID: 3.进入目录:cd /usr ...
- 从零开始的全栈工程师——html篇1
全栈工程师也可以叫web 前端 H5主要是网站 app 小程序 公众号这一块 HTML篇 html(超文本标记语言,标记通用标记语言下的一个应用.) “超文本”就是指页面内可以包含图片.链接,甚至音乐 ...
- 对json缓存进行操作
var data={ id:1, name:"张三" } //存储缓存 var arrdata=[]; arrdata.push({id:data.id,name:data.nam ...
- Web服务器父与子 Apache和Tomcat
Apache是世界使用排名第一的Web服务器软件.它可以运行在几乎所有广泛使用的计算机平台上,由于其跨平台和安全性被广泛使用,是最流行的Web服务器端软件之一.在Apache基金会里面Apache S ...